diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb
index d51e92f31..534b65903 100644
--- a/admin/app/admin/jam_tracks.rb
+++ b/admin/app/admin/jam_tracks.rb
@@ -5,9 +5,14 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
config.sort_order = 'name_asc'
config.batch_actions = false
- filter :genre
+ filter :genres
filter :status, :as => :select, collection: JamRuby::JamTrack::STATUS
+ scope("Default", default: true) { |scope| scope }
+ scope("Onboarding TODO") { |scope| scope.where('onboarding_exceptions is not null') }
+ scope("Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'") }
+ scope("Onboarding TODO w/ Tency Only") { |scope| scope.joins('INNER JOIN jam_track_licensors as licensors ON jam_tracks.licensor_id = licensors.id').where("licensors.name = 'Tency Music'").where('onboarding_exceptions is not null') }
+
form :partial => 'form'
index do
@@ -24,11 +29,21 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
column :original_artist
column :name
- column :onboarding_flags do |jam_track| jam_track.onboard_warnings end
+ column :onboarding_exceptions do |jam_track|
+ if jam_track.onboarding_exceptions
+ exceptions = JSON.parse(jam_track.onboarding_exceptions)
+ exceptions.keys.join(',')
+ else
+ ''
+ end
+
+ end
column :status
column :master_track do |jam_track| jam_track.master_track.nil? ? 'None' : (link_to "Download", jam_track.master_track.url_by_sample_rate(44)) end
column :licensor
- column :genre
+ column :genres do |jam_track|
+ jam_track.genres.map(&:description).join(',')
+ end
column :price
column :reproduction_royalty
column :public_performance_royalty
diff --git a/admin/app/views/admin/jam_tracks/_form.html.slim b/admin/app/views/admin/jam_tracks/_form.html.slim
index 51341d812..334cdc27e 100644
--- a/admin/app/views/admin/jam_tracks/_form.html.slim
+++ b/admin/app/views/admin/jam_tracks/_form.html.slim
@@ -12,7 +12,7 @@
= f.input :songwriter, :input_html => { :rows=>1, :maxlength=>1000 }
= f.input :publisher, :input_html => { :rows=>1, :maxlength=>1000 }
= f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: true
- = f.input :genre, collection: JamRuby::Genre.all, include_blank: false
+ = f.input :genres
= f.input :duration, hint: 'this should rarely need editing because it comes from the import process'
= f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false
= f.input :price, :required => true, :input_html => {type: 'numeric'}
diff --git a/admin/config/initializers/jam_tracks.rb b/admin/config/initializers/jam_tracks.rb
index cd02eccee..33efd995c 100644
--- a/admin/config/initializers/jam_tracks.rb
+++ b/admin/config/initializers/jam_tracks.rb
@@ -16,7 +16,6 @@ class JamRuby::JamTrack
end
def jmep_json_generate
- self.genre_id = nil if self.genre_id == ''
self.licensor_id = nil if self.licensor_id == ''
self.jmep_json = nil if self.jmep_json == ''
self.time_signature = nil if self.time_signature == ''
diff --git a/db/manifest b/db/manifest
index 1c648aa07..4a74f3869 100755
--- a/db/manifest
+++ b/db/manifest
@@ -299,3 +299,5 @@ enhance_band_profile.sql
alter_band_profile_rate_defaults.sql
repair_band_profile.sql
profile_teacher.sql
+jam_track_onboarding_enhancements.sql
+jam_track_name_drop_unique.sql
diff --git a/db/up/jam_track_name_drop_unique.sql b/db/up/jam_track_name_drop_unique.sql
new file mode 100644
index 000000000..d283b34cd
--- /dev/null
+++ b/db/up/jam_track_name_drop_unique.sql
@@ -0,0 +1 @@
+ALTER TABLE jam_tracks DROP CONSTRAINT jam_tracks_name_key;
\ No newline at end of file
diff --git a/db/up/jam_track_onboarding_enhancements.sql b/db/up/jam_track_onboarding_enhancements.sql
new file mode 100644
index 000000000..2a373eb5c
--- /dev/null
+++ b/db/up/jam_track_onboarding_enhancements.sql
@@ -0,0 +1,78 @@
+-- "rsvp_slots_instrument_id_fkey" FOREIGN KEY (instrument_id) REFERENCES instruments(id)
+-- "musicians_instruments_instrument_id_fkey" FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE CASCADE
+-- "saved_tracks_instrument_id_fkey" FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE CASCADE
+ALTER TABLE rsvp_slots DROP CONSTRAINT rsvp_slots_instrument_id_fkey;
+ALTER TABLE musicians_instruments DROP CONSTRAINT musicians_instruments_instrument_id_fkey;
+ALTER TABLE recorded_tracks DROP CONSTRAINT saved_tracks_instrument_id_fkey;
+UPDATE instruments SET id = 'double bass', description = 'Double Bass' WHERE id = 'upright bass';
+UPDATE rsvp_slots SET instrument_id = 'double bass' where instrument_id = 'upright bass';
+UPDATE musicians_instruments SET instrument_id = 'double bass' where instrument_id = 'upright bass';
+UPDATE recorded_tracks SET instrument_id = 'double bass' where instrument_id = 'upright bass';
+ALTER TABLE rsvp_slots ADD CONSTRAINT rsvp_slots_instrument_id_fkey FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE SET NULL;
+ALTER TABLE musicians_instruments ADD CONSTRAINT musicians_instruments_instrument_id_fkey FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE CASCADE;
+ALTER TABLE recorded_tracks ADD CONSTRAINT saved_tracks_instrument_id_fkey FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE CASCADE;
+
+INSERT INTO instruments (id, description, popularity) VALUES ('steel guitar', 'Steel Guitar', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('orchestra', 'Orchestra', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('glockenspiel', 'Glockenspiel', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('dobro', 'Dobro', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('harp', 'Harp', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('vocoder', 'Vocoder', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('flugelhorn', 'Flugelhorn', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('timpani', 'Timpani', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('bassoon', 'Bassoon', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('charango', 'Charango', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('theremin', 'Theremin', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('sitar', 'Sitar', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('piccolo', 'Piccolo', 1);
+INSERT INTO instruments (id, description, popularity) VALUES ('bagpipes', 'Bagpipes', 1);
+ALTER TABLE jam_tracks ADD COLUMN onboarding_exceptions JSON;
+ALTER TABLE jam_track_tracks ADD COLUMN original_filename VARCHAR;
+ALTER TABLE jam_tracks ADD COLUMN additional_info VARCHAR;
+ALTER TABLE jam_tracks ADD COLUMN language VARCHAR NOT NULL DEFAULT 'eng';
+ALTER TABLE jam_tracks ADD COLUMN year INTEGER;
+ALTER TABLE jam_tracks ADD COLUMN vendor_id VARCHAR;
+
+INSERT INTO jam_track_licensors (name, description) VALUES ('Tency Music', 'Tency Music is a music production company specialized in re-recordings.');
+
+CREATE TABLE genres_jam_tracks (
+ id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
+ jam_track_id VARCHAR(64) NOT NULL REFERENCES jam_tracks(id) ON DELETE CASCADE,
+ genre_id VARCHAR(64) NOT NULL REFERENCES genres(id) ON DELETE CASCADE,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+INSERT INTO genres_jam_tracks (jam_track_id, genre_id) ((SELECT jam_tracks.id, jam_tracks.genre_id FROM jam_tracks));
+ALTER TABLE jam_tracks DROP COLUMN genre_id;
+
+-- holds precount, click.wav, click.txt
+CREATE TABLE jam_track_files (
+ id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
+ jam_track_id VARCHAR(64) REFERENCES jam_tracks(id) ON DELETE CASCADE,
+ file_type VARCHAR NOT NULL,
+ original_filename VARCHAR NOT NULL,
+ precount_num INTEGER,
+ url VARCHAR,
+ md5 VARCHAR,
+ length bigint,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+INSERT INTO genres (id, description) VALUES ('soft rock', 'Soft Rock');
+INSERT INTO genres (id, description) VALUES ('rap', 'Rap');
+INSERT INTO genres (id, description) VALUES ('tv & movie soundtrack', 'TV & Movie Soundtrack');
+INSERT INTO genres (id, description) VALUES ('holiday', 'Holiday');
+INSERT INTO genres (id, description) VALUES ('kids', 'Kids');
+INSERT INTO genres (id, description) VALUES ('disco', 'Disco');
+INSERT INTO genres (id, description) VALUES ('soul', 'Soul');
+INSERT INTO genres (id, description) VALUES ('hard rock', 'Hard Rock');
+INSERT INTO genres (id, description) VALUES ('funk', 'Funk');
+INSERT INTO genres (id, description) VALUES ('dance', 'Dance');
+INSERT INTO genres (id, description) VALUES ('creole', 'Creole');
+INSERT INTO genres (id, description) VALUES ('traditional', 'Traditional');
+INSERT INTO genres (id, description) VALUES ('oldies', 'Oldies');
+INSERT INTO genres (id, description) VALUES ('world', 'World');
+INSERT INTO genres (id, description) VALUES ('musical', 'Musical');
+INSERT INTO genres (id, description) VALUES ('celtic', 'Celtic');
diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb
index 16ae29942..574e114d5 100755
--- a/ruby/lib/jam_ruby.rb
+++ b/ruby/lib/jam_ruby.rb
@@ -206,6 +206,8 @@ require "jam_ruby/models/jam_track"
require "jam_ruby/models/jam_track_track"
require "jam_ruby/models/jam_track_right"
require "jam_ruby/models/jam_track_tap_in"
+require "jam_ruby/models/jam_track_file"
+require "jam_ruby/models/genre_jam_track"
require "jam_ruby/app/mailers/async_mailer"
require "jam_ruby/app/mailers/batch_mailer"
require "jam_ruby/app/mailers/progress_mailer"
@@ -237,11 +239,14 @@ require "jam_ruby/jmep_manager"
require "jam_ruby/models/performance_sample"
require "jam_ruby/models/online_presence"
require "jam_ruby/models/json_store"
+require "jam_ruby/models/base_search"
require "jam_ruby/models/musician_search"
require "jam_ruby/models/teacher"
require "jam_ruby/models/teacher_experience"
require "jam_ruby/models/language"
require "jam_ruby/models/subject"
+require "jam_ruby/models/band_search"
+require "jam_ruby/import/tency_stem_mapping"
include Jampb
diff --git a/ruby/lib/jam_ruby/import/tency_stem_mapping.rb b/ruby/lib/jam_ruby/import/tency_stem_mapping.rb
new file mode 100644
index 000000000..769496562
--- /dev/null
+++ b/ruby/lib/jam_ruby/import/tency_stem_mapping.rb
@@ -0,0 +1,360 @@
+module JamRuby
+
+ # this is probably a one-off class used to map Tency-named stems into JamKazam-named stems
+ class TencyStemMapping
+
+ @@log = Logging.logger[TencyStemMapping]
+
+ def s3_manager
+ @s3_manager ||= S3Manager.new('jamkazam-tency', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
+ end
+
+ def initialize
+ @originals_folder = "/Volumes/sethcall/Dropbox/seth@jamkazam.com/JamTracks - Tency Music - Original Folder for Normalization Map"
+ @mapping_folder = "/Volumes/sethcall/Dropbox/seth@jamkazam.com/JamTracks - Tency Music"
+ @original_songs = {}
+ @mapping_songs = {}
+ @mappings = {}
+ end
+
+ def create_map
+ tency_originals
+ tency_maps
+
+ dump
+ end
+
+ def create_mapping_map
+ tency_maps
+
+ dump_map
+ end
+
+ def hydrate
+ @original_songs = YAML.load_file('original_songs.yml')
+ @mapping_songs = YAML.load_file('mapping_songs.yml')
+ end
+
+ def parse_sanitized_filename(filename)
+ instrument = nil
+ part = nil
+
+ basename = File.basename(filename)
+ stem = basename.index('Stem')
+
+ if stem
+ stripped = basename[(stem + 'Stem'.length)..-5] # takes of 'stem' and '.wav'
+ stripped.strip!
+ dash = stripped.index('-')
+
+ if dash == 0
+ stripped = stripped[1..-1].strip!
+ # now we should have something like "Vocal - Lead" (instrument - part)
+ instrument, part = stripped.split('-')
+ instrument.strip! if instrument
+ part.strip! if part
+ else
+ "no or misplaced dash for #{filename}"
+ end
+
+ else
+ raise "no stem for #{filename}"
+ end
+
+ [instrument, part]
+ end
+
+ # For all the tracks that I have labeled manually as
+ # Instrument = Upright Bass and Part = Upright Bass,
+ # can you please change both the Instrument and Part to Double Bass instead?
+ #
+ def check_mappings
+ missing_instrument = 0
+ missing_part = 0
+ part_names = []
+
+ hydrate
+ @mapping_songs.each do |cache_id, data|
+ mapped_filename = data[:filename]
+ @@log.debug("parsing #{mapped_filename}")
+ instrument, part = parse_sanitized_filename(mapped_filename)
+ @@log.debug("parsed #{instrument} (#{part})")
+ missing_instrument = missing_instrument + 1 unless instrument
+ missing_part = missing_part + 1 unless part
+ part_names << mapped_filename unless part
+ end
+
+ @@log.info("SUMMARY")
+ @@log.info("-------")
+ @@log.info("missing instruments:#{missing_instrument} missing parts: #{missing_part}")
+ @@log.info("files with no parts: #{part_names}")
+
+ # files with no parts:
+ # ["Huey Lewis And The News - Heart And Soul - 31957/Heart And Soul Stem - Synth 2.wav",
+ # "ZZ Top - Tush - 20852/Tush Stem - Clicktrack.wav",
+ # "Crosby Stills And Nash - Teach Your Children - 15440/Teach Your Children Stem - Bass Guitar.wav",
+ # /Brad Paisley - She's Everything - 19886/She's Everything Stem - Clicktrack.wav",
+ # "Toby Keith - Beer For My Horses - 7221/Beer For My Horses Stem - Lap Steel.wav",
+ # Toby Keith - Beer For My Horses - 7221/Beer For My Horses Stem - Acoustic Guitar.wav"
+
+ end
+
+ def track_mapping(basename, instr_part)
+ instrument = instr_part[:instrument]
+ part = instr_part[:part]
+
+ basename.downcase!
+
+ info = @mappings[basename]
+
+ unless info
+ info = {matches:[]}
+ @mappings[basename] = info
+ end
+
+ info[:matches] << instr_part
+ end
+
+ def correlate
+ mapped = 0
+ unmapped = 0
+ unmapped_details = []
+ no_instrument = []
+ common_unknown_instruments = {}
+
+ hydrate
+ @mapping_songs.each do |cache_id, data|
+ # go through each track hand-mapped, and find it's matching song if any.
+
+ mapped_filename = data[:filename]
+ found_original = @original_songs[cache_id]
+ if found_original
+ # mapping made
+
+ original_filename = found_original[:filename]
+ original_basename = File.basename(original_filename).downcase
+
+ mapped = mapped + 1
+
+ instrument, part = parse_sanitized_filename(mapped_filename)
+ instr_part = JamTrackImporter.determine_instrument(instrument, part)
+
+ instr_part[:instrument]
+
+ if instr_part[:instrument]
+
+ # track the mapping of this one
+ track_mapping(original_basename, instr_part)
+
+ else
+ @@log.error("unable to determine instrument for #{File.basename(mapped_filename)}")
+ no_instrument << ({filename: File.basename(mapped_filename), instrument: instrument, part: part})
+ common_unknown_instruments["#{instrument}-(#{part})"] = 1
+ end
+
+ else
+ unmapped = unmapped + 1
+ unmapped_details << {filename: mapped_filename}
+ end
+ end
+
+ puts("SUMMARY")
+ puts("-------")
+ puts("MAPPED:#{mapped} UNMAPPED:#{unmapped}")
+ unmapped_details.each do |unmapped_detail|
+ puts "UNMAPPED FILE: #{File.basename(unmapped_detail[:filename])}"
+ end
+ puts("UNKNOWN INSTRUMENT: #{no_instrument.length}")
+ no_instrument.each do |item|
+ puts("UNKNOWN INSTRUMENT: #{item[:filename]}")
+ end
+ common_unknown_instruments.each do |key, value|
+ puts("#{key}")
+ end
+ @mappings.each do |basename, mapping|
+ matches = mapping[:matches]
+ counts = matches.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }
+ ordered_matches = counts.sort_by {|k, v| -v}
+ output = ""
+ ordered_matches.each do |match|
+ detail = match[0]
+ count = match[1]
+ output << "#{detail[:instrument]}(#{detail[:part]})/#{count}, "
+ end
+
+ puts "map detail: #{basename}: #{output}"
+
+ mapping[:ordered] = ordered_matches
+ mapping[:detail] = output
+ end
+ CSV.open("mapping.csv", "wb") do |csv|
+ @mappings.each do |basename, mapping|
+ item = mapping[:ordered]
+
+ trust_worthy = item.length == 1
+ unless trust_worthy
+ # if the 1st item is at least 4 'counts' more than the next item, we can consider it trust_worthy
+ if item[0][1] - 4 > item[1][1]
+ trust_worthy = true
+ end
+ end
+ csv << [ basename, item[0][0][:instrument], item[0][0][:part], item[0][1], trust_worthy ]
+ end
+ end
+ CSV.open("determinate-single-matches.csv", "wb") do |csv|
+ @mappings.each do |basename, mapping|
+ if mapping[:ordered].length == 1 && mapping[:ordered][0][1] == 1
+ item = mapping[:ordered]
+ csv << [ basename, item[0][0][:instrument], item[0][0][:part], item[0][1] ]
+ end
+ end
+ end
+ CSV.open("determinate-multi-matches.csv", "wb") do |csv|
+ @mappings.each do |basename, mapping|
+ if mapping[:ordered].length == 1 && mapping[:ordered][0][1] > 1
+ item = mapping[:ordered]
+ csv << [ basename, item[0][0][:instrument], item[0][0][:part], item[0][1] ]
+ end
+ end
+ end
+ CSV.open("ambiguous-matches.csv", "wb") do |csv|
+ @mappings.each do |basename, mapping|
+ if mapping[:ordered].length > 1
+ csv << [ basename, mapping[:detail] ]
+ end
+ end
+ end
+ end
+
+ def dump
+ File.open('original_songs.yml', 'w') {|f| f.write(YAML.dump(@original_songs)) }
+ File.open('mapping_songs.yml', 'w') {|f| f.write(YAML.dump(@mapping_songs)) }
+ end
+ def dump_map
+ File.open('mapping_songs.yml', 'w') {|f| f.write(YAML.dump(@mapping_songs)) }
+ end
+
+ def md5(filepath)
+ Digest::MD5.file(filepath).hexdigest
+ end
+
+ def tency_original_check
+ songs = Pathname.new(@originals_folder).children.select { |c| c.directory? }
+ songs.each do |song|
+ dirs = Pathname.new(song).children.select {|c| c.directory? }
+
+ @@log.debug "SONG #{song}"
+ dirs.each do |dir|
+ @@log.debug "#{dir.basename.to_s}"
+ end
+ @@log.debug ""
+ end
+ end
+
+ def tency_originals
+ songs = Pathname.new(@originals_folder).children.select { |c| c.directory? }
+ songs.each do |filename|
+ id = parse_id(filename.basename.to_s )
+ files = Pathname.new(filename).children.select {|c| c.file? }
+
+ # also look into any 1st level folders we might find
+
+ dirs = Pathname.new(filename).children.select {|c| c.directory? }
+ dirs.each do |dir|
+ more_tracks = Pathname.new(dir).children.select {|c| c.file? }
+ files = files + more_tracks
+ end
+
+ files.each do |file|
+ @@log.debug("processing original track #{file.to_s}")
+ md5 = md5(file.to_s)
+ song = {md5:md5, filename:file.to_s, id:id}
+ @original_songs[cache_id(id, md5)] = song
+ end
+ end
+
+ end
+
+ def tency_maps
+ songs = Pathname.new(@mapping_folder).children.select { |c| c.directory? }
+ songs.each do |song_filename|
+ id = parse_id_mapped(song_filename.basename.to_s )
+ @@log.debug "processing song #{song_filename.to_s}"
+
+ tracks = Pathname.new(song_filename).children.select {|c| c.file? }
+ tracks.each do |track|
+ if track.to_s.include? "Stem"
+ @@log.debug("processing mapped track #{track.to_s}")
+ md5 = md5(track.to_s)
+
+ song = {md5:md5, filename:track.to_s}
+ @mapping_songs[cache_id(id, md5)] = song
+ end
+ end
+ end
+ end
+
+ def cache_id(id, md5)
+ "#{id}-#{md5}"
+ end
+
+ def parse_id(filename)
+ #amy-winehouse_you-know-i-m-no-good-feat-ghostface-killah_11767
+
+ index = filename.rindex('_')
+ if index
+ id = filename[(index + 1)..-1]
+
+ if id.end_with?('/')
+ id = id[0...-1]
+ end
+
+ id = id.to_i
+
+ if id == 0
+ raise "no valid ID in filename: #{filename}"
+ end
+ else
+ raise "no _ in filename: #{filename}"
+ end
+ id
+ end
+
+ def parse_id_mapped(filename)
+ #Flyleaf - I'm So Sick - 15771
+
+ index = filename.rindex('-')
+ if index
+ id = filename[(index + 1)..-1]
+
+ if id.end_with?('/')
+ id = id[0...-1]
+ end
+
+ id.strip!
+
+ id = id.to_i
+
+ if id == 0
+ raise "no valid ID in filename: #{filename}"
+ end
+ else
+ raise "no - in filename: #{filename}"
+ end
+ id
+ end
+
+
+
+ def tency_originals2
+ s3_manager.list_directories('mapper').each do |song_folder|
+ @@log.debug("searching through tency directory. song folder:'#{song_folder}'")
+
+ id = parse_id(song_folder)
+ @@log.debug("ID #{id}")
+
+ top_folder = s3_manager.list_directories(song_folder)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb
index 70ef72e6a..fc34eeac3 100644
--- a/ruby/lib/jam_ruby/jam_track_importer.rb
+++ b/ruby/lib/jam_ruby/jam_track_importer.rb
@@ -14,6 +14,7 @@ module JamRuby
attr_accessor :name
attr_accessor :reason
attr_accessor :detail
+ attr_accessor :storage_format
def jamkazam_s3_manager
@s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
@@ -23,11 +24,140 @@ module JamRuby
@public_s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_public, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
+ def initialize(storage_format = 'default')
+ @storage_format = storage_format
+ end
+
def finish(reason, detail)
self.reason = reason
self.detail = detail
end
+
+ # this method was created due to Tency-sourced data having no master track
+ # it goes through all audio tracks, and creates a master mix from it. (mix + normalize)
+ def create_master(metadata, metalocation)
+
+ parsed_metalocation = parse_metalocation(metalocation)
+
+ if parsed_metalocation.nil?
+ finish("invalid_metalocation", metalocation)
+ return
+ end
+
+ original_artist = parsed_metalocation[1]
+ meta_name = parsed_metalocation[2]
+
+ self.name = metadata[:name] || meta_name
+
+
+ audio_path = metalocation[0...-"/meta.yml".length]
+
+ all_files = fetch_important_files(audio_path)
+
+ audio_files = []
+ master_found = false
+ all_files.each do |file|
+
+ parsed_wav = parse_file(file)
+ if parsed_wav[:master]
+ master_found = true
+ elsif parsed_wav[:type] == :track
+
+ audio_files << file
+ end
+ end
+
+ if master_found
+ @@log.debug("master exists... skipping #{self.name} ")
+ finish('success', nil)
+ return
+ else
+
+ tracks = []
+
+ #tmp_dir = Dir.mktmpdir
+ #tmp_dir = "/var/folders/05/1jpzfcln1hq9p666whnd7chr0000gn/T/d20150809-9945-1ykr85u"
+ Dir.mktmpdir do |tmp_dir|
+ @@log.debug("downloading all audio files in #{tmp_dir}")
+ audio_files.each do |s3_track|
+ track = File.join(tmp_dir, File.basename(s3_track))
+ tracks << track
+ JamTrackImporter.song_storage_manager.download(s3_track, track)
+ end
+
+ # first have to check if all are the same sample rate. If not, we have to make it so
+
+ first_sample_rate = nil
+ normalize_needed = false
+ tracks.each do |track|
+ sample_rate = `soxi -r "#{track}"`.strip
+
+ if first_sample_rate.nil?
+ first_sample_rate = sample_rate
+ else
+ if first_sample_rate != sample_rate
+ # we need to normalize all of them
+ normalize_needed = true
+ break
+ end
+ end
+ end
+
+ normalized_tracks = []
+ if normalize_needed
+ tracks.each do |track|
+ normalized_track = File.join(tmp_dir, 'normalized-' + File.basename(track))
+ output = `sox "#{track}" "#{normalized_track}" rate #{first_sample_rate}`
+ @@log.debug("resampling #{normalized_track}; output: #{output}")
+ normalized_tracks << normalized_track
+ end
+ tracks = normalized_tracks
+ end
+
+
+
+ temp_file = File.join(tmp_dir, "temp.wav")
+ output_filename = JamTrackImporter.remove_s3_special_chars("#{self.name} Master Mix.wav")
+ output_file = File.join(tmp_dir, output_filename)
+ command = "sox -m "
+ tracks.each do |track|
+ command << " \"#{track}\""
+ end
+ command << " \"#{temp_file}\""
+
+ @@log.debug("mixing with cmd: " + command)
+ sox_output = `#{command}`
+ result_code = $?.to_i
+
+ if result_code != 0
+ @@log.error("unable to generate master mix")
+ finish("sox_master_mix_failure", sox_output)
+ else
+
+ # now normalize the audio
+
+ command = "sox --norm \"#{temp_file}\" \"#{output_file}\""
+ @@log.debug("normalizing with cmd: " + command)
+ sox_output = `#{command}`
+ result_code = $?.to_i
+ if result_code != 0
+ @@log.error("unable to normalize master mix")
+ finish("sox_master_mix_failure", sox_output)
+ else
+
+ # now we need to upload the output back up
+ s3_target = audio_path + '/' + output_filename
+ @@log.debug("uploading #{output_file} to #{s3_target}")
+ JamTrackImporter.song_storage_manager.upload(s3_target, output_file )
+ finish('success', nil)
+ end
+
+ end
+ end
+ end
+ end
+
def dry_run(metadata, metalocation)
metadata ||= {}
@@ -38,35 +168,92 @@ module JamRuby
original_artist = parsed_metalocation[1]
name = parsed_metalocation[2]
+ JamTrackImporter.summaries[:unique_artists] << original_artist
+
success = dry_run_metadata(metadata, original_artist, name)
return unless success
- dry_run_audio(metadata, "audio/#{original_artist}/#{name}")
+ audio_path = metalocation[0...-"/meta.yml".length]
+
+
+ dry_run_audio(metadata, audio_path)
finish("success", nil)
end
+ def is_tency_storage?
+ assert_storage_set
+ @storage_format == 'Tency'
+ end
+
+ def assert_storage_set
+ raise "no storage_format set" if @storage_format.nil?
+ end
+
+
def parse_metalocation(metalocation)
+ # metalocation = mapped/4 Non Blondes - What's Up - 6475/meta.yml
+ if is_tency_storage?
- bits = metalocation.split('/')
+ suffix = '/meta.yml'
- if bits.length != 4
- finish("invalid_metalocation", "metalocation not valid #{metalocation}")
- return nil
+ unless metalocation.end_with? suffix
+ finish("invalid_metalocation", "metalocation not valid #{metalocation}")
+ return nil
+ end
+
+ metalocation = metalocation[0...-suffix.length]
+
+ first_path = metalocation.index('/')
+ if first_path.nil?
+ finish("invalid_metalocation", "metalocation not valid #{metalocation}")
+ return nil
+ end
+ metalocation = metalocation[(first_path + 1)..-1]
+
+ bits = ['audio']
+ # example: Sister Hazel - All For You - 10385
+ first_dash = metalocation.index(' - ')
+ if first_dash
+ artist = metalocation[0...(first_dash)].strip
+ bits << artist
+ else
+ finish("invalid_metalocation", "metalocation not valid #{metalocation}")
+ return nil
+ end
+
+ last_dash = metalocation.rindex('-')
+ if last_dash
+ song = metalocation[(first_dash+3)...last_dash].strip
+ bits << song
+ else
+ finish("invalid_metalocation", "metalocation not valid #{metalocation}")
+ return nil
+ end
+
+ bits << 'meta.yml'
+ bits
+ else
+ bits = metalocation.split('/')
+
+ if bits.length != 4
+ finish("invalid_metalocation", "metalocation not valid #{metalocation}")
+ return nil
+ end
+
+ if bits[0] != "audio"
+ finish("invalid_metalocation", "first bit is not 'audio' #{metalocation}")
+ return nil
+ end
+
+ if bits[3] != 'meta.yml'
+ finish('invalid_metalocation', "last bit is not 'meta.yml' #{metalocation}")
+ return nil
+ end
+
+ bits
end
-
- if bits[0] != "audio"
- finish("invalid_metalocation", "first bit is not 'audio' #{metalocation}")
- return nil
- end
-
- if bits[3] != 'meta.yml'
- finish('invalid_metalocation', "last bit is not 'meta.yml' #{metalocation}")
- return nil
- end
-
- bits
end
# if you change this, it will (at least without some work )break development usage of jamtracks
@@ -91,27 +278,154 @@ module JamRuby
true
end
+ def determine_genres(metadata)
+
+ genres = []
+ if metadata[:genres]
+ metadata[:genres].each do |genre|
+ if genre == 'hard/metal'
+ genres << Genre.find('hard rock')
+ genres << Genre.find('metal')
+ elsif genre == 'christmas'
+ genres << Genre.find('holiday')
+ elsif genre == 'alternative'
+ genres << Genre.find('alternative rock')
+ elsif genre == '80s'
+ # swallow
+ elsif genre == 'love'
+ # swallow
+ elsif genre == 'christian' || genre == 'gospel'
+ genres << Genre.find('religious')
+ elsif genre == 'punk/grunge'
+ genres << Genre.find('punk')
+ elsif genre == 'electro'
+ genres << Genre.find('electronic')
+ elsif genre == 'teen pop'
+ genres << Genre.find('pop')
+ elsif genre == "rock 'n roll"
+ genres << Genre.find('rock')
+ elsif genre == 'zouk/creole'
+ genres << Genre.find('creole')
+ elsif genre == 'world/folk'
+ genres << Genre.find('world')
+ genres << Genre.find('folk')
+ elsif genre == 'french pop'
+ # swallow
+ elsif genre == 'schlager'
+ #swallow
+ elsif genre == 'humour'
+ # swallow
+ elsif genre == 'oriental'
+ genres << Genre.find('asian')
+ else
+ found = Genre.find_by_id(genre)
+ genres << found if found
+ end
+
+ end
+ end
+
+ genres
+ end
+
+ def determine_language(metadata)
+
+ found = ISO_639.find_by_code('eng')
+
+ language = metadata[:language]
+
+ if language
+ language.downcase!
+
+ if language == 'instrumental'
+ return 'instrumental'
+ end
+
+ if language.include? 'spanish'
+ found = ISO_639.find_by_code('spa')
+ elsif language.include? 'german'
+ found = ISO_639.find_by_code('ger')
+ elsif language.include? 'portuguese'
+ found = ISO_639.find_by_code('por')
+ elsif language.include? 'english'
+ found = ISO_639.find_by_code('eng')
+ end
+ end
+
+ found[0] # 3 letter code
+ end
+
+ #http://stackoverflow.com/questions/22740252/how-to-generate-javas-string-hashcode-using-ruby
+ def jhash(str)
+ result = 0
+ mul = 1
+ max_mod = 2**31 - 1
+
+ str.chars.reverse_each do |c|
+ result += mul * c.ord
+ result %= max_mod
+ mul *= 31
+ end
+
+ result
+ end
+
+ def prevent_concurrent_processing(metalocation)
+
+ # use a PG advisory lock to see if someone else is doing this same unit of work right now
+ track_code = jhash(metalocation)
+ locked = ActiveRecord::Base.connection.execute("SELECT pg_try_advisory_xact_lock(#{track_code})").values[0][0]
+ if locked == 'f'
+ finish("other_processing", "")
+ raise ActiveRecord::Rollback
+ end
+ end
+
def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name, options)
metadata ||= {}
self.name = metadata["name"] || name
+ prevent_concurrent_processing(metalocation)
+
if jam_track.new_record?
latest_jamtrack = JamTrack.order('created_at desc').first
+
id = latest_jamtrack.nil? ? 1 : latest_jamtrack.id.to_i + 1
+ if ENV['NODE_NUMBER']
+ # complicated goofy code to support parallel processing of importers
+
+ node_number = ENV['NODE_NUMBER'].to_i
+ node_count = ENV['NODE_COUNT'].to_i
+ raise "NO NODE_COUNT" if node_count == 0
+ r = id % node_count
+ id = r + id # get to the same base number if both are working at the same time
+ id = id + node_number # offset by your node number
+ @@log.debug("JAM TRACK ID: #{id}")
+ end
jam_track.id = "#{id}" # default is UUID, but the initial import was based on auto-increment ID, so we'll maintain that
jam_track.status = 'Staging'
jam_track.metalocation = metalocation
jam_track.original_artist = metadata["original_artist"] || original_artist
jam_track.name = self.name
- jam_track.genre_id = 'rock'
+ jam_track.additional_info = metadata[:additional_info]
+ jam_track.year = metadata[:year]
+ jam_track.genres = determine_genres(metadata)
+ jam_track.language = determine_language(metadata)
jam_track.plan_code = metadata["plan_code"] || gen_plan_code(jam_track.original_artist, jam_track.name)
jam_track.price = 1.99
- jam_track.reproduction_royalty_amount = 0
- jam_track.licensor_royalty_amount = 0
+ jam_track.reproduction_royalty_amount = nil
+ jam_track.reproduction_royalty = true
+ jam_track.public_performance_royalty = true
+ jam_track.licensor_royalty_amount = 0.4
jam_track.sales_region = 'Worldwide'
jam_track.recording_type = 'Cover'
jam_track.description = "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the #{jam_track.original_artist} song \"#{jam_track.name}\"."
+
+ if is_tency_storage?
+ jam_track.vendor_id = metadata[:id]
+ jam_track.licensor = JamTrackLicensor.find_by_name('Tency Music')
+ end
else
if !options[:resync_audio]
#@@log.debug("#{self.name} skipped because it already exists in database")
@@ -125,6 +439,7 @@ module JamRuby
end
+ @@log.debug("about to save")
saved = jam_track.save
if !saved
@@ -167,12 +482,18 @@ module JamRuby
else
instrument = 'electric guitar'
end
- elsif potential_instrument == 'electric gutiar' || potential_instrument == 'electric guitat'
+ elsif potential_instrument == 'acoustic'
+ instrument = 'acoustic guitar'
+ elsif potential_instrument == 'acoutic guitar'
+ instrument = 'electric guitar'
+ elsif potential_instrument == 'electric gutiar' || potential_instrument == 'electric guitat' || potential_instrument == 'electric guitary'
instrument = 'electric guitar'
elsif potential_instrument == 'keys'
instrument = 'keyboard'
elsif potential_instrument == 'vocal' || potential_instrument == 'vocals'
instrument = 'voice'
+ elsif potential_instrument == 'upright bass'
+ instrument = 'double bass'
elsif potential_instrument == 'bass'
instrument = 'bass guitar'
elsif potential_instrument == 'drum'
@@ -185,8 +506,9 @@ module JamRuby
else
part = 'Sound FX'
end
-
-
+ elsif potential_instrument == 'computer scratches'
+ instrument = 'computer'
+ part = 'Scratches'
elsif potential_instrument == "sax"
instrument = 'saxophone'
elsif potential_instrument == "vocal back up"
@@ -213,18 +535,43 @@ module JamRuby
elsif potential_instrument == 'fretless bass'
instrument = 'bass guitar'
part = 'Fretless'
+ elsif potential_instrument == 'lap steel' || potential_instrument == 'pedal steel'
+ instrument = 'steel guitar'
elsif potential_instrument == 'clock percussion'
instrument = 'computer'
part = 'Clock'
- elsif potential_instrument == 'horns'
+ elsif potential_instrument == 'horns' || potential_instrument == 'horn'
instrument = 'other'
- part = 'Horns'
+ part = 'Horns' if potential_part.nil?
+ elsif potential_instrument == 'english horn'
+ instrument = 'other'
+ part = 'English Horn'
+ elsif potential_instrument == 'bass clarinet'
+ instrument = 'other'
+ part = 'Bass Clarinet'
+ elsif potential_instrument == 'recorder'
+ instrument = 'other'
+ part = 'Recorder'
+ elsif potential_instrument == 'marimba'
+ instrument = 'keyboard'
+ part = 'Marimba'
elsif potential_instrument == 'strings'
- instrument = 'other'
+ instrument = 'orchestra'
part = 'Strings'
- elsif potential_instrument == 'orchestration'
- instrument = 'computer'
- part = 'Orchestration'
+ elsif potential_instrument == 'celesta'
+ instrument = 'keyboard'
+ elsif potential_instrument == 'balalaika'
+ instrument = 'other'
+ part = 'Balalaika'
+ elsif potential_instrument == 'tanpura'
+ instrument = 'other'
+ part = 'Tanpura'
+ elsif potential_instrument == 'quena'
+ instrument = 'other'
+ part = 'Quena'
+ elsif potential_instrument == 'bouzouki'
+ instrument = 'other'
+ part = 'Bouzouki'
elsif potential_instrument == 'claps' || potential_instrument == 'hand claps'
instrument = 'computer'
part = 'Claps'
@@ -246,20 +593,44 @@ module JamRuby
end
- def parse_wav(file)
+ def parse_file(file)
bits = file.split('/')
filename = bits[bits.length - 1] # remove all but just the filename
filename_no_ext = filename[0..-5]
comparable_filename = filename_no_ext.downcase # remove .wav
+ type = nil
master = false
instrument = nil
part = nil
+ precount_num = nil
+ no_precount_detail = nil
+ if comparable_filename == "click" || comparable_filename.include?("clicktrack")
+ if filename.end_with?('.txt')
+ type = :clicktxt
+ else
+ type = :clickwav
+ end
+ elsif comparable_filename.include? "precount"
+ type = :precount
+ index = comparable_filename.index('precount')
+ precount = comparable_filename[(index + 'precount'.length)..-1].strip
+ if precount.start_with?('_')
+ precount = precount[1..-1]
+ end
+ if precount.to_i == 0
+ no_precount_detail = comparable_filename
+ else
+ precount_num = precount.to_i
+ end
- if comparable_filename.include?("master mix") || comparable_filename.include?("mastered mix")
+
+ elsif comparable_filename.include?("master mix") || comparable_filename.include?("mastered mix")
master = true
+ type = :master
else
+ type = :track
stem_location = comparable_filename.index('stem -')
unless stem_location
stem_location = comparable_filename.index('stems -')
@@ -294,72 +665,209 @@ module JamRuby
result = determine_instrument(possible_instrument, possible_part)
instrument = result[:instrument]
part = result[:part]
+ else
+ if is_tency_storage?
+ # we can check to see if we can find mapping info for this filename
+ mapping = JamTrackImporter.tency_mapping[filename.downcase]
+
+ if mapping && mapping[:trust]
+ instrument = mapping[:instrument]
+ part = mapping[:part]
+ end
+
+ # tency mapping didn't work; let's retry with our own home-grown mapping
+ if instrument.nil? && !possible_instrument.nil?
+ result = determine_instrument(possible_instrument, possible_part)
+ instrument = result[:instrument]
+ part = result[:part]
+ end
+ end
end
+
end
- {filename: filename, master: master, instrument: instrument, part: part}
+ {filename: filename, master: master, instrument: instrument, part: part, type: type, precount_num: precount_num, no_precount_detail: no_precount_detail}
end
def dry_run_audio(metadata, s3_path)
- all_files = fetch_wav_files(s3_path)
+ all_files = fetch_important_files(s3_path)
all_files.each do |file|
- if file.end_with?('.wav')
- parsed_wav = parse_wav(file)
- if parsed_wav[:master]
- @@log.debug("#{self.name} master! filename: #{parsed_wav[:filename]}")
- else
- if !parsed_wav[:instrument] || !parsed_wav[:part]
- @@log.warn("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
- else
- @@log.debug("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
+
+ # ignore click/precount
+ parsed_wav = parse_file(file)
+ if parsed_wav[:master]
+ @@log.debug("#{self.name} master! filename: #{parsed_wav[:filename]}")
+ elsif parsed_wav[:type] == :track
+
+ JamTrackImporter.summaries[:total_tracks] += 1
+
+ if parsed_wav[:instrument].nil?
+ detail = JamTrackImporter.summaries[:no_instrument_detail]
+ file_detail = detail[parsed_wav[:filename].downcase]
+ if file_detail.nil?
+ detail[parsed_wav[:filename].downcase] = 0
end
+ detail[parsed_wav[:filename].downcase] += 1
+
+ JamTrackImporter.summaries[:no_instrument] += 1
end
+
+ JamTrackImporter.summaries[:no_part] += 1 if parsed_wav[:part].nil?
+
+ if !parsed_wav[:instrument] || !parsed_wav[:part]
+ @@log.warn("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
+ else
+ @@log.debug("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
+ end
+ elsif parsed_wav[:type] == :clickwav
+
+ elsif parsed_wav[:type] == :clicktxt
+
+ elsif parsed_wav[:type] == :precount
+ if parsed_wav[:precount_num].nil?
+ JamTrackImporter.summaries[:no_precount_num] += 1
+ JamTrackImporter.summaries[:no_precount_detail] << parsed_wav[:no_precount_detail]
+ end
+
else
- @@log.debug("#{self.name} ignoring non-wav file #{file}")
+ JamTrackImporter.summaries[:unknown_filetype] += 1
end
end
-
end
- def sort_tracks(tracks)
- def set_custom_weight(track)
- weight = 5
- # if there are any persisted tracks, do not sort from scratch; just stick new stuff at the end
+ def set_custom_weight(track)
- if track.persisted?
- weight = track.position
+ slop = 800
+
+ instrument_weight = nil
+ # if there are any persisted tracks, do not sort from scratch; just stick new stuff at the end
+
+ if track.persisted?
+ instrument_weight = track.position
+ else
+ if track.instrument_id == 'voice'
+
+ if track.part && track.part.start_with?('Lead')
+ instrument_weight = 100
+ elsif track.part && track.part.start_with?('Backing')
+ instrument_weight = 110
+ else
+ instrument_weight = 120
+ end
+
+ elsif track.instrument_id == 'drums'
+
+ if track.part && track.part == 'Drums'
+ instrument_weight = 150
+ elsif track.part && track.part == 'Percussion'
+ instrument_weight = 160
+ else
+ instrument_weight = 170
+ end
+
+ elsif track.instrument_id == 'bass guitar' && track.part && track.part == 'Bass'
+ instrument_weight = 180
+
+ elsif track.instrument_id == 'piano' && track.part && track.part == 'Piano'
+ instrument_weight = 250
+
+ elsif track.instrument_id == 'keyboard'
+
+ if track.part && track.part.start_with?('Synth')
+ instrument_weight = 260
+ elsif track.part && track.part.start_with?('Pads')
+ instrument_weight = 270
+ else
+ instrument_weight = 280
+ end
+
+ elsif track.instrument_id == 'acoustic guitar'
+ if track.part && track.part.start_with?('Lead')
+ instrument_weight = 300
+ elsif track.part && track.part.start_with?('Rhythm')
+ instrument_weight = 310
+ else
+ instrument_weight = 320
+ end
+ elsif track.instrument_id == 'electric guitar'
+ if track.part && track.part.start_with?('Lead')
+ instrument_weight = 400
+ elsif track.part && track.part.start_with?('Solo')
+ instrument_weight = 410
+ elsif track.part && track.part.start_with?('Rhythm')
+ instrument_weight = 420
+ else
+ instrument_weight = 440
+ end
else
- case track.instrument_id
- when 'electric guitar'
- weight = 100
- when 'acoustic guitar'
- weight = 200
- when 'drums'
- weight = 300
- when 'keys'
- weight = 400
- when 'computer'
- weight = 600
- else
- weight = 500
- end
- if track.track_type == 'Master'
- weight = 1000
- end
+ instrument_weight = slop
end
-
- weight
+ if track.track_type == 'Master'
+ instrument_weight = 1000
+ end
end
+
+ instrument_weight
+ end
+
+ def deduplicate_parts(tracks)
+ unique_instruments = {}
+
+ tracks.each do |track|
+
+ key = "#{track.instrument_id} | #{track.part}"
+ found = unique_instruments[key]
+ if !found
+ found = []
+ unique_instruments[key] = found
+ end
+
+ found << track
+ end
+
+ unique_instruments.each do |key, value|
+ if value.length > 1
+ count = 0
+
+ value.each do |track|
+ if track.part.nil?
+ track.part = (count + 1).to_s
+ else
+ track.part = "#{track.part} #{count + 1}"
+ end
+ count += 1
+ end
+
+ end
+ end
+
+ # debug output
+ tracks.each do |track|
+ puts "TRACK #{track.instrument_id} #{track.part}"
+ end
+ end
+
+
+ def sort_tracks(tracks)
+
sorted_tracks = tracks.sort do |a, b|
a_weight = set_custom_weight(a)
b_weight = set_custom_weight(b)
- a_weight <=> b_weight
+ if a_weight != b_weight
+ a_weight <=> b_weight
+ elsif a.instrument_id != b.instrument_id
+ a.instrument_id <=> b.instrument_id
+ else
+ a_part = a.part
+ b_part = b.part
+ a_part <=> b_part
+ end
end
# default to 1, but if there are any persisted tracks, this will get manipulated to be +1 the highest persisted track
@@ -387,9 +895,10 @@ module JamRuby
attempt_to_match_existing_tracks = true
# find all wav files in the JamTracks s3 bucket
- wav_files = fetch_wav_files(s3_path)
+ wav_files = fetch_important_files(s3_path)
tracks = []
+ addt_files = []
wav_files.each do |wav_file|
@@ -419,27 +928,51 @@ module JamRuby
@@log.debug("no existing track found; creating a new one")
track = JamTrackTrack.new
+ track.original_filename = wav_file
track.original_audio_s3_path = wav_file
- parsed_wav = parse_wav(wav_file)
+ file = JamTrackFile.new
+ file.original_filename = wav_file
+ file.original_audio_s3_path = wav_file
+ parsed_wav = parse_file(wav_file)
+
+ unknowns = 0
if parsed_wav[:master]
track.track_type = 'Master'
- track.part = 'Master'
+ track.part = 'Master Mix'
+ track.instrument_id = 'computer'
+ tracks << track
@@log.debug("#{self.name} master! filename: #{parsed_wav[:filename]}")
- else
+ elsif parsed_wav[:type] == :track
+
if !parsed_wav[:instrument] || !parsed_wav[:part]
@@log.warn("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
+ unknowns += 1
else
@@log.debug("#{self.name} track! instrument: #{parsed_wav[:instrument] ? parsed_wav[:instrument] : 'N/A'}, part: #{parsed_wav[:part] ? parsed_wav[:part] : 'N/A'}, filename: #{parsed_wav[:filename]} ")
end
+
track.instrument_id = parsed_wav[:instrument] || 'other'
track.track_type = 'Track'
- track.part = parsed_wav[:part] || 'Other'
+ track.part = parsed_wav[:part] || "Other #{unknowns}"
+ tracks << track
+ elsif parsed_wav[:type] == :clicktxt
+ file.file_type = 'ClickTxt'
+ addt_files << file
+ elsif parsed_wav[:type] == :clickwav
+ file.file_type = 'ClickWav'
+ addt_files << file
+ elsif parsed_wav[:type] == :precount
+ file.file_type = 'Precount'
+ file.precount_num = parsed_wav[:precount_num]
+ addt_files << file
+ else
+ finish("unknown_file_type", "unknown file type #{wave_file}")
+ return false
end
- tracks << track
end
jam_track.jam_track_tracks.each do |jam_track_track|
@@ -450,10 +983,20 @@ module JamRuby
end
end
+ jam_track.jam_track_files.each do |jam_track_file|
+ unless addt_files.include?(jam_track_file)
+ @@log.info("destroying removed JamTrackFile #{jam_track_file.inspect}")
+ jam_track_file.destroy # should also delete s3 files associated with this jamtrack
+ end
+ end
+
@@log.info("sorting tracks")
tracks = sort_tracks(tracks)
+ deduplicate_parts(tracks)
+
jam_track.jam_track_tracks = tracks
+ jam_track.jam_track_files = addt_files
saved = jam_track.save
@@ -505,7 +1048,7 @@ module JamRuby
wav_file = File.join(tmp_dir, basename)
# bring the original wav file down from S3 to local file system
- JamTrackImporter::s3_manager.download(track.original_audio_s3_path, wav_file)
+ JamTrackImporter::song_storage_manager.download(track.original_audio_s3_path, wav_file)
sample_rate = `soxi -r "#{wav_file}"`.strip
@@ -553,7 +1096,10 @@ module JamRuby
if !preview_succeeded
return false
end
+ elsif track.track_type == 'Track'
+ synchronize_track_preview(track, tmp_dir, ogg_44100)
end
+
end
track.save!
@@ -582,6 +1128,68 @@ module JamRuby
true
end
+ def synchronize_track_preview(track, tmp_dir, ogg_44100)
+
+ out_wav = File.join(tmp_dir, 'stripped.wav')
+
+ burp_gaps = ['0.3', '0.2', '0.1', '0.05']
+
+ total_time_command = "soxi -D \"#{ogg_44100}\""
+ total_time = `#{total_time_command}`.to_f
+
+ result_code = -20
+ stripped_time = total_time # default to the case where we just start the preview at the beginning
+
+ burp_gaps.each do |gap|
+ command_strip_lead_silence = "sox \"#{ogg_44100}\" \"#{out_wav}\" silence 1 #{gap} 1%"
+
+ @@log.debug("stripping silence: " + command_strip_lead_silence)
+
+ output = `#{command_strip_lead_silence}`
+
+ result_code = $?.to_i
+
+ if result_code == 0
+ stripped_time_command = "soxi -D \"#{out_wav}\""
+ stripped_time_test = `#{stripped_time_command}`.to_f
+
+ if stripped_time_test < 1 # meaning a very short duration
+ @@log.warn("could not determine the start of non-silencea. assuming beginning")
+ stripped_time = total_time # default to the case where we just start the preview at the beginning
+ else
+ stripped_time = stripped_time_test # accept the measured time of the stripped file and move on by using break
+ break
+ end
+ else
+ @@log.warn("unable to determine silence for jam_track #{track.original_filename}, #{output}")
+ stripped_time = total_time # default to the case where we just start the preview at the beginning
+ end
+
+ end
+
+ preview_start_time = total_time - stripped_time
+
+ # this is in seconds; convert to integer milliseconds
+ preview_start_time = (preview_start_time * 1000).to_i
+
+ preview_start_time = nil if preview_start_time < 0
+
+ track.preview_start_time = preview_start_time
+
+ if track.preview_start_time
+ @@log.debug("determined track start time to be #{track.preview_start_time}")
+ else
+ @@log.debug("determined track start time to be #{track.preview_start_time}")
+ end
+
+ track.process_preview(ogg_44100, tmp_dir) if track.preview_start_time
+
+ if track.preview_generate_error
+ @@log.warn(track.preview_generate_error)
+ end
+
+ end
+
def synchronize_master_preview(track, tmp_dir, ogg_44100, ogg_digest)
begin
@@ -639,12 +1247,12 @@ module JamRuby
end
def fetch_all_files(s3_path)
- JamTrackImporter::s3_manager.list_files(s3_path)
+ JamTrackImporter::song_storage_manager.list_files(s3_path)
end
- def fetch_wav_files(s3_path)
+ def fetch_important_files(s3_path)
files = fetch_all_files(s3_path)
- files.select { |file| file.end_with?('.wav') }
+ files.select { |file| file.end_with?('.wav') || file.end_with?('.txt') }
end
def synchronize(jam_track, metadata, metalocation, options)
@@ -664,7 +1272,9 @@ module JamRuby
return unless success
- synchronized_audio = synchronize_audio(jam_track, metadata, "audio/#{original_artist}/#{name}", options[:skip_audio_upload])
+ audio_path = metalocation[0...-"/meta.yml".length]
+
+ synchronized_audio = synchronize_audio(jam_track, metadata, audio_path, options[:skip_audio_upload])
return unless synchronized_audio
@@ -673,6 +1283,9 @@ module JamRuby
finish("success", nil)
end
+ # do a last check on any problems with the jamtrack
+ jam_track.sync_onboarding_exceptions
+
end
def synchronize_recurly(jam_track)
@@ -690,16 +1303,205 @@ module JamRuby
class << self
+ attr_accessor :storage_format
+ attr_accessor :tency_mapping
+ attr_accessor :tency_metadata
+ attr_accessor :summaries
+
+ def report_summaries
+ @@log.debug("SUMMARIES DUMP")
+ @@log.debug("--------------")
+ @summaries.each do |k, v|
+
+ if k == :no_instrument_detail
+ @@log.debug("#{k}: #{v}")
+ elsif k == :no_precount_detail
+ v.each do |precount_detail|
+ @@log.debug("precount: #{precount_detail}")
+ end
+ elsif k == :unique_artists
+ v.each do |artist|
+ @@log.debug("artist: #{artist}")
+ end
+ else
+ @@log.debug("#{k}: #{v}")
+ end
+ end
+ end
+
+ def song_storage_manager
+ if is_tency_storage?
+ tency_s3_manager
+ else
+ s3_manager
+ end
+ end
+
+ def summaries
+ @summaries ||= {unknown_filetype: 0, no_instrument: 0, no_part: 0, total_tracks: 0, no_instrument_detail: {}, no_precount_num: 0, no_precount_detail: [], unique_artists: SortedSet.new}
+ end
+
+ def tency_s3_manager
+ @tency_s3_manager ||= S3Manager.new('jamkazam-tency', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
+ end
+
def s3_manager
@s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_jamtracks, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
def private_s3_manager
- @s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
+ @private_s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
+ def extract_tency_song_id(metalocation)
+ # metalocation = mapped/4 Non Blondes - What's Up - 6475/meta.yml
+
+ first_path = metalocation.index('/')
+ return nil unless first_path
+ metalocation = metalocation[(first_path + 1)..-1]
+
+ suffix = '/meta.yml'
+ metalocation = metalocation[0...-suffix.length]
+
+ last_dash = metalocation.rindex('-')
+ return nil if last_dash.nil?
+
+ id = metalocation[(last_dash+1)..-1].strip
+
+ return nil if id.to_i == 0
+
+ id
+ end
+
+ def is_tency_storage?
+ assert_storage_set
+ @storage_format == 'Tency'
+ end
+
+ def assert_storage_set
+ raise "no storage_format set" if @storage_format.nil?
+ end
+
+ def iterate_tency_song_storage(&blk)
+ count = 0
+ song_storage_manager.list_directories('mapped').each do |song|
+ @@log.debug("searching through song directory '#{song}'")
+
+ metalocation = "#{song}meta.yml"
+
+ metadata = load_metalocation(metalocation)
+
+ blk.call(metadata, metalocation)
+
+ count += 1
+ #break if count > 100
+
+ end
+ end
+
+ def iterate_default_song_storage(&blk)
+ song_storage_manager.list_directories('audio').each do |original_artist|
+ @@log.debug("searching through artist directory '#{original_artist}'")
+
+ songs = song_storage_manager.list_directories(original_artist)
+ songs.each do |song|
+ @@log.debug("searching through song directory' #{song}'")
+
+ metalocation = "#{song}meta.yml"
+
+ metadata = load_metalocation(metalocation)
+
+ blk.call(metadata, metalocation)
+ end
+ end
+ end
+
+ def iterate_song_storage(&blk)
+ if is_tency_storage?
+ iterate_tency_song_storage do |metadata, metalocation|
+ blk.call(metadata, metalocation)
+ end
+ else
+ iterate_default_song_storage do |metadata, metalocation|
+ blk.call(metadata, metalocation)
+ end
+ end
+ end
def dry_run
+ iterate_song_storage do |metadata, metalocation|
+ jam_track_importer = JamTrackImporter.new(@storage_format)
+
+ jam_track_importer.dry_run(metadata, metalocation)
+ end
+
+ report_summaries
+ end
+
+ # figure out which songs are in S3 that do not exist in the 2k spreadsheet (mapping.csv), and which songs are in the 2k spreadsheet that are not in S3
+ def tency_delta
+ in_s3 = {}
+ in_mapping = {}
+
+ load_tency_mappings
+
+ JamTrackImporter.tency_metadata.each do |song_id, metadata|
+ in_mapping[song_id] = {artist: metadata[:original_artist], song: metadata[:name]}
+ end
+
+ iterate_song_storage do |metadata, metalocation|
+
+ importer = JamTrackImporter.new(@storage_format)
+ song_id = JamTrackImporter.extract_tency_song_id(metalocation)
+ parsed_metalocation = importer.parse_metalocation(metalocation)
+
+ next if song_id.nil?
+ next if parsed_metalocation.nil?
+
+ original_artist = parsed_metalocation[1]
+ meta_name = parsed_metalocation[2]
+
+ in_s3[song_id] = {artist: original_artist, song: meta_name}
+ end
+
+ in_s3_keys = Set.new(in_s3.keys)
+ in_mapping_keys = Set.new(in_mapping.keys)
+ only_in_mapping = in_mapping_keys - in_s3_keys
+ only_in_s3 = in_s3_keys - in_mapping_keys
+
+ CSV.open("only_in_s3.csv", "wb") do |csv|
+ only_in_s3.each do |song_id|
+ csv << [ song_id, in_s3[song_id][:artist], in_s3[song_id][:song] ]
+ end
+ end
+
+ CSV.open("only_in_2k_selection.csv", "wb") do |csv|
+ only_in_mapping.each do |song_id|
+ csv << [ song_id, in_mapping[song_id][:artist], in_mapping[song_id][:song] ]
+ end
+ end
+
+ end
+ def create_masters
+ iterate_song_storage do |metadata, metalocation|
+ next if metadata.nil?
+ jam_track_importer = JamTrackImporter.new(@storage_format)
+
+ jam_track_importer.create_master(metadata, metalocation)
+ end
+ end
+
+ def create_master(path)
+ metalocation = "#{path}/meta.yml"
+
+ metadata = load_metalocation(metalocation)
+
+ jam_track_importer = JamTrackImporter.new(@storage_format)
+
+ jam_track_importer.create_master(metadata, metalocation)
+ end
+
+ def dry_run_original
s3_manager.list_directories('audio').each do |original_artist|
@@log.debug("searching through artist directory '#{original_artist}'")
@@ -805,7 +1607,7 @@ module JamRuby
@@log.info("-------")
importers.each do |importer|
if importer
- if importer.reason == "success" || importer.reason == "jam_track_exists"
+ if importer.reason == "success" || importer.reason == "jam_track_exists" || importer.reason == "other_processing"
@@log.info("#{importer.name} #{importer.reason}")
else
@@log.error("#{importer.name} failed to import.")
@@ -852,7 +1654,7 @@ module JamRuby
@@log.info("-------")
importers.each do |importer|
if importer
- if importer.reason == "success" || importer.reason == "jam_track_exists"
+ if importer.reason == "success" || importer.reason == "jam_track_exists" || importer.reason == "other_processing"
@@log.info("#{importer.name} #{importer.reason}")
else
@@log.error("#{importer.name} failed to import.")
@@ -906,28 +1708,40 @@ module JamRuby
end
end
+ def remove_s3_special_chars(filename)
+ filename.tr('/&@:,$=+?;\^`><{}[]#%~|', '')
+ end
+ def onboarding_exceptions
+ JamTrack.all.each do |jam_track|
+ jam_track.onboarding_exceptions
+ end
+ end
def synchronize_all(options)
importers = []
- s3_manager.list_directories('audio').each do |original_artist|
- @@log.debug("searching through artist directory '#{original_artist}'")
+ count = 0
+ iterate_song_storage do |metadata, metalocation|
- songs = s3_manager.list_directories(original_artist)
- songs.each do |song|
- @@log.debug("searching through song directory' #{song}'")
+ next if metadata.nil? && is_tency_storage?
- metalocation = "#{song}meta.yml"
+ importer = synchronize_from_meta(metalocation, options)
+ importers << importer
- importer = synchronize_from_meta(metalocation, options)
- importers << importer
+ if importer.reason != 'jam_track_exists' && importer.reason != "other_processing"
+ count+=1
+ end
+
+ if count > 500
+ break
end
end
+
@@log.info("SUMMARY")
@@log.info("-------")
importers.each do |importer|
if importer
- if importer.reason == "success" || importer.reason == "jam_track_exists"
+ if importer.reason == "success" || importer.reason == "jam_track_exists" || importer.reason == "other_processing"
@@log.info("#{importer.name} #{importer.reason}")
else
@@log.error("#{importer.name} failed to import.")
@@ -956,12 +1770,113 @@ module JamRuby
end
end
+ def genre_dump
+ load_tency_mappings
+
+ genres = {}
+ @tency_metadata.each do |id, value|
+
+ genre1 = value[:genre1]
+ genre2 = value[:genre2]
+ genre3 = value[:genre3]
+ genre4 = value[:genre4]
+ genre5 = value[:genre5]
+
+ genres[genre1.downcase.strip] = genre1.downcase.strip if genre1
+ genres[genre2.downcase.strip] = genre2.downcase.strip if genre2
+ genres[genre3.downcase.strip] = genre3.downcase.strip if genre3
+ genres[genre4.downcase.strip] = genre4.downcase.strip if genre4
+ genres[genre5.downcase.strip] = genre5.downcase.strip if genre5
+ end
+
+ all_genres = Genre.select(:id).all.map(&:id)
+
+ all_genres = Set.new(all_genres)
+ genres.each do |genre, value|
+ found = all_genres.include? genre
+
+ puts "#{genre}" unless found
+ end
+ end
+
+ def load_tency_mappings
+ Dir.mktmpdir do |tmp_dir|
+ mapping_file = File.join(tmp_dir, 'mapping.csv')
+ metadata_file = File.join(tmp_dir, 'metadata.csv')
+
+ # this is a developer option to skip the download and look in the CWD to grab mapping.csv and metadata.csv
+ if ENV['TENCY_ALREADY_DOWNLOADED'] == '1'
+ mapping_file = 'mapping.csv'
+ metadata_file = 'metadata.csv'
+ else
+ tency_s3_manager.download('mapping/mapping.csv', mapping_file)
+ tency_s3_manager.download('mapping/metadata.csv', metadata_file)
+ end
+
+ mapping_csv = CSV.read(mapping_file)
+ metadata_csv = CSV.read(metadata_file, headers: true, return_headers: false)
+
+ @tency_mapping = {}
+ @tency_metadata = {}
+ # convert both to hashes
+ mapping_csv.each do |line|
+ @tency_mapping[line[0].strip] = {instrument: line[1], part: line[2], count: line[3], trust: line[4]}
+ end
+
+ metadata_csv.each do |line|
+ @tency_metadata[line[0].strip] = {id: line[0].strip, original_artist: line[1], name: line[2], additional_info: line[3], year: line[4], language: line[5], isrc: line[10], genre1: line[11], genre2: line[12], genre3: line[13], genre4: line[14], genre5: line[15]}
+ end
+
+
+ @tency_metadata.each do |id, value|
+
+ genres = []
+
+ genre1 = value[:genre1]
+ genre2 = value[:genre2]
+ genre3 = value[:genre3]
+ genre4 = value[:genre4]
+ genre5 = value[:genre5]
+
+ genres << genre1.downcase.strip if genre1
+ genres << genre2.downcase.strip if genre2
+ genres << genre3.downcase.strip if genre3
+ genres << genre4.downcase.strip if genre4
+ genres << genre5.downcase.strip if genre5
+
+ value[:genres] = genres
+ end
+
+
+ end
+ end
+
def load_metalocation(metalocation)
- begin
- data = s3_manager.read_all(metalocation)
- return YAML.load(data)
- rescue AWS::S3::Errors::NoSuchKey
- return nil
+
+ if is_tency_storage?
+ load_tency_mappings if @tency_mapping.nil?
+ song_id = extract_tency_song_id(metalocation)
+
+ if song_id.nil?
+ puts "missing_song_id #{metalocation}"
+ return nil
+ end
+
+
+ tency_data = @tency_metadata[song_id]
+
+ if tency_data.nil?
+ @@log.warn("missing tency metadata '#{song_id}'")
+ end
+
+ return tency_data
+ else
+ begin
+ data = s3_manager.read_all(metalocation)
+ return YAML.load(data)
+ rescue AWS::S3::Errors::NoSuchKey
+ return nil
+ end
end
end
@@ -975,9 +1890,12 @@ module JamRuby
end
def sync_from_metadata(jam_track, meta, metalocation, options)
- jam_track_importer = JamTrackImporter.new
+ jam_track_importer = JamTrackImporter.new(@storage_format)
+
+ JamTrack.connection.execute('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED')
JamTrack.transaction do
+
#begin
jam_track_importer.synchronize(jam_track, meta, metalocation, options)
#rescue Exception => e
@@ -998,6 +1916,9 @@ module JamRuby
meta = load_metalocation(metalocation)
+ if meta.nil? && is_tency_storage?
+ raise "no tency song matching this metalocation #{metalocation}"
+ end
jam_track_importer = nil
if jam_track
@@log.debug("jamtrack #{jam_track.name} located by metalocation")
diff --git a/ruby/lib/jam_ruby/models/band.rb b/ruby/lib/jam_ruby/models/band.rb
index c3d4b471d..2ba858c6e 100644
--- a/ruby/lib/jam_ruby/models/band.rb
+++ b/ruby/lib/jam_ruby/models/band.rb
@@ -71,6 +71,7 @@ module JamRuby
end
def follower_count
+ # FIXME: this could be a lot of followers; calling size loads all the data into memory
self.followers.size
end
diff --git a/ruby/lib/jam_ruby/models/band_search.rb b/ruby/lib/jam_ruby/models/band_search.rb
new file mode 100644
index 000000000..976250935
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/band_search.rb
@@ -0,0 +1,428 @@
+module JamRuby
+ class BandSearch < BaseSearch
+
+ cattr_accessor :jschema, :search_meta
+ attr_accessor :user_counters
+
+ serialize :data_blob, JSON
+
+ KEY_BAND_SEARCH_TYPE = 'band_search_type'
+ KEY_BAND_TYPE = 'band_type'
+ KEY_BAND_STATUS = 'band_status'
+ KEY_PLAY_COMMIT = 'play_commitment'
+ KEY_TOUR_OPTION = 'touring_option'
+ KEY_PERF_SAMPLES = 'performance_samples'
+ KEY_HIRE_MAX_COST = 'max_cost'
+ KEY_HIRE_FREE = 'free_gigs'
+
+ TO_JOIN = 'to_join'
+ TO_HIRE = 'to_hire'
+
+ BAND_SEARCH_TYPE_VALS = [TO_JOIN, TO_HIRE]
+ BAND_SEARCH_TYPES = {
+ TO_JOIN => 'search bands',
+ TO_HIRE => 'search bands to hire',
+ }
+
+ BAND_TYPE_VAL_STRS = [ANY_VAL_STR, 'amateur', 'professional']
+ BAND_TYPES = {
+ BAND_TYPE_VAL_STRS[0] => BAND_TYPE_VAL_STRS[0].camelcase,
+ BAND_TYPE_VAL_STRS[1] => BAND_TYPE_VAL_STRS[1].camelcase,
+ BAND_TYPE_VAL_STRS[2] => BAND_TYPE_VAL_STRS[2].camelcase,
+ }
+
+ SORT_VALS = %W{ distance }
+ SORT_ORDERS = {
+ SORT_VALS[0] => 'Distance to Me'
+ }
+ # SORT_VALS = %W{ distance latency }
+ # SORT_ORDERS = {
+ # SORT_VALS[0] => 'Distance to Me'
+ # SORT_VALS[1] => 'Latency to Me',
+ # }
+
+ HIRE_SORT_VALS = %W{ distance price_asc price_desc }
+ HIRE_SORT_ORDERS = {
+ HIRE_SORT_VALS[0] => 'Distance to Me',
+ HIRE_SORT_VALS[1] => 'Gig Minimum Price (Low to High)',
+ HIRE_SORT_VALS[2] => 'Gig Minimum Price (High to Low)',
+ }
+
+ BAND_STATUS_VALS = [ANY_VAL_STR,
+ GenrePlayer::VIRTUAL_BAND,
+ GenrePlayer::TRADITIONAL_BAND,
+ ]
+ BAND_STATUS = {
+ BAND_STATUS_VALS[0] => 'Any',
+ BAND_STATUS_VALS[1] => 'Virtual Band',
+ BAND_STATUS_VALS[2] => 'Traditional Band',
+ }
+
+ PLAY_COMMIT_VALS = [ANY_VAL_STR,
+ '1',
+ '2',
+ '3',
+ '4',
+ ]
+ PLAY_COMMITS = {
+ PLAY_COMMIT_VALS[0] => 'Any',
+ PLAY_COMMIT_VALS[1] => 'Infrequent',
+ PLAY_COMMIT_VALS[2] => 'Once a Week',
+ PLAY_COMMIT_VALS[3] => '2-3 Times Per Week',
+ PLAY_COMMIT_VALS[4] => '4+ Times Per Week',
+ }
+
+ TOUR_OPTION_VALS = [ANY_VAL_STR,
+ VAL_YES,
+ VAL_NO,
+ ]
+ TOUR_OPTIONS = {
+ TOUR_OPTION_VALS[0] => 'Any',
+ TOUR_OPTION_VALS[1] => VAL_YES,
+ TOUR_OPTION_VALS[2] => VAL_NO,
+ }
+
+ PERF_SAMPLES_VALS = TOUR_OPTION_VALS.clone
+ PERF_SAMPLES = TOUR_OPTIONS.clone
+
+ COUNT_FOLLOW = :count_follow
+ COUNT_RECORD = :count_record
+ COUNT_SESSION = :count_session
+
+ def self.json_schema
+ return @@jschema if @@jschema
+ @@jschema = {
+ TO_JOIN => BaseSearch.json_schema.merge({
+ KEY_SORT_ORDER => self::SORT_VALS[0],
+ KEY_BAND_TYPE => self::BAND_TYPE_VAL_STRS[0].to_s,
+ KEY_BAND_STATUS => BAND_STATUS_VALS[0],
+ KEY_PLAY_COMMIT => PLAY_COMMIT_VALS[0],
+ KEY_TOUR_OPTION => TOUR_OPTION_VALS[0],
+ }),
+ TO_HIRE => {
+ KEY_SORT_ORDER => self::HIRE_SORT_VALS[0],
+ KEY_GENRES => [],
+ KEY_GIGS => self::GIG_COUNTS[0].to_s,
+ KEY_BAND_STATUS => BAND_STATUS_VALS[0],
+ KEY_PERF_SAMPLES => self::PERF_SAMPLES_VALS[0],
+ KEY_HIRE_MAX_COST => 0,
+ KEY_HIRE_FREE => 0,
+ },
+ }
+ end
+
+ def self.search_filter_meta
+ return @@search_meta if @@search_meta
+ toJoinMeta = super(self.json_schema[TO_JOIN])
+ toJoinMeta.merge!({
+ KEY_BAND_TYPE => { keys: BAND_TYPE_VAL_STRS, map: BAND_TYPES },
+ KEY_BAND_STATUS => { keys: BAND_STATUS_VALS, map: BAND_STATUS },
+ KEY_PLAY_COMMIT => { keys: PLAY_COMMIT_VALS, map: PLAY_COMMITS },
+ KEY_TOUR_OPTION => { keys: TOUR_OPTION_VALS, map: TOUR_OPTIONS }
+ })
+ toHireMeta = super(self.json_schema[TO_HIRE],
+ { keys: HIRE_SORT_VALS, map: HIRE_SORT_ORDERS })
+ toHireMeta.merge!({
+ KEY_BAND_STATUS => { keys: BAND_STATUS_VALS, map: BAND_STATUS },
+ KEY_PERF_SAMPLES => { keys: PERF_SAMPLES_VALS, map: PERF_SAMPLES },
+ })
+ @@search_meta = {
+ TO_JOIN => toJoinMeta,
+ TO_HIRE => toHireMeta,
+ }
+ end
+
+ def self.search_target_class
+ Band
+ end
+
+ def _genres(rel, filter)
+ super(rel, filter)
+ end
+
+ def _concert_gigs(rel, filter)
+ gg = filter[KEY_GIGS].to_i
+ rel = rel.where(concert_count: gg) if 0 <= gg
+ rel
+ end
+
+ def _band_status(rel, filter)
+ case filter[KEY_BAND_STATUS]
+ when GenrePlayer::VIRTUAL_BAND
+ rel.where(band_status: GenrePlayer::VIRTUAL_BAND.sub('_band',''))
+ when GenrePlayer::TRADITIONAL_BAND
+ rel.where(band_status: GenrePlayer::TRADITIONAL_BAND.sub('_band',''))
+ else
+ rel
+ end
+ end
+
+ def _play_commitment(rel, filter)
+ unless ANY_VAL_STR == filter[KEY_PLAY_COMMIT]
+ rel = rel.where(play_commitment: filter[KEY_PLAY_COMMIT].to_i)
+ end
+ rel
+ end
+
+ def _touring_option(rel, filter)
+ case filter[KEY_TOUR_OPTION]
+ when VAL_YES
+ rel.where(touring_option: true)
+ when VAL_NO
+ rel.where(touring_option: false)
+ else
+ rel
+ end
+ end
+
+ def _performance_samples(rel, filter)
+ case filter[KEY_PERF_SAMPLES]
+ when VAL_YES
+ rel.joins("LEFT OUTER JOIN performance_samples AS ps ON ps.player_id = bands.id AND player_type = '#{Band.name}'").where(["ps.id IS NOT NULL"])
+ when VAL_NO
+ rel.joins("LEFT OUTER JOIN performance_samples AS ps ON ps.player_id = bands.id AND player_type = '#{Band.name}'").where(["ps.id IS NULL"])
+ else
+ rel
+ end
+ end
+
+ def _max_cost(rel, filter)
+ if 0 < (max_cost = filter[KEY_HIRE_MAX_COST].to_i)
+ col = Band.arel_table[:gig_minimum]
+ rel = rel.where(col.lteq(max_cost)).where(col.gt(0))
+ end
+ rel
+ end
+
+ def _free_gigs(rel, filter)
+ case filter[KEY_HIRE_FREE]
+ when VAL_YES
+ rel.where(free_gigs: true)
+ when VAL_NO
+ rel.where(free_gigs: false)
+ else
+ rel
+ end
+ end
+
+ def _band_type(rel, filter)
+ case filter[KEY_BAND_TYPE]
+ when BAND_TYPE_VAL_STRS[1]
+ rel.where(band_type: BAND_TYPE_VAL_STRS[1])
+ when BAND_TYPE_VAL_STRS[2]
+ rel.where(band_type: BAND_TYPE_VAL_STRS[2])
+ else
+ rel
+ end
+ end
+
+ def _sort_order(rel, filter)
+ val = filter[KEY_SORT_ORDER]
+ if 'distance' == val || val.blank?
+ locidispid = self.user.last_jam_locidispid || 0
+ my_locid = locidispid / 1000000
+ rel = rel.joins("LEFT JOIN geoiplocations AS my_geo ON my_geo.locid = #{my_locid}")
+ rel = rel.joins("LEFT JOIN geoiplocations AS other_geo ON other_geo.latitude = bands.lat AND other_geo.longitude = bands.lng")
+ rel = rel.group("bands.id, my_geo.geog, other_geo.geog")
+ rel = rel.order('st_distance(my_geo.geog, other_geo.geog)')
+
+ elsif 'price_asc' == val
+ rel = rel.order('gig_minimum ASC')
+
+ elsif 'price_desc' == val
+ rel = rel.order('gig_minimum DESC')
+ end
+ rel
+ end
+
+ def do_search(filter)
+ rel = Band.unscoped
+ filter.keys.each do |fkey|
+ mname = "_#{fkey}"
+ if self.respond_to?(mname)
+ rel = self.send(mname.to_sym, rel, filter)
+ end
+ end
+ rel
+ end
+
+ def search_includes(rel, subtype=TO_JOIN)
+ TO_JOIN == subtype ? rel.includes([:instruments]) : rel
+ end
+
+ def _process_results_page(_results)
+ @results = _results
+ if user
+ @user_counters = @results.inject({}) { |hh,val| hh[val.id] = {}; hh }
+
+ # this gets counts for each search result
+ @results.each do |bb|
+ counters = {
+ COUNT_FOLLOW => Follow.where(:followable_id => bb.id).count,
+ COUNT_RECORD => Recording.where(:band_id => bb.id).count,
+ COUNT_SESSION => MusicSession.where(:band_id => bb.id).count
+ }
+ @user_counters[bb.id] = counters
+ end
+ else
+ @user_counters = {}
+ end
+ self
+ end
+
+ private
+
+ def _count(band, key)
+ if mm = @user_counters[band.id]
+ return mm[key]
+ end if @user_counters
+ 0
+ end
+
+ public
+
+ def follow_count(band)
+ _count(band, COUNT_FOLLOW)
+ end
+
+ def record_count(band)
+ _count(band, COUNT_RECORD)
+ end
+
+ def session_count(band)
+ _count(band, COUNT_SESSION)
+ end
+
+ def is_follower?(band)
+ if mm = @user_counters[band.id]
+ return mm.include?(RESULT_FOLLOW)
+ end if @user_counters
+ false
+ end
+
+ def search_type
+ self.class.to_s
+ end
+
+ def is_blank?(subtype=TO_JOIN)
+ self.search_filter_for_subtype(subtype) == self.class.json_schema[subtype]
+ end
+
+ def reset_filter(subtype, data=nil)
+ data ||= self.class.json_schema[subtype]
+ dblob = self.data_blob
+ dblob[subtype] = data
+ self.data_blob = dblob
+ self.save
+ end
+
+ def reset_search_results(subtype=TO_JOIN)
+ reset_filter(subtype)
+ search_results_page(subtype)
+ end
+
+ def self.search_filter_json(user, subtype=TO_JOIN)
+ self.user_search_filter(user).json[subtype]
+ end
+
+ def search_filter_for_subtype(subtype)
+ self.data_blob[subtype]
+ end
+
+ def search_results_page(subtype=TO_JOIN, filter=nil, page=1)
+ if filter
+ reset_filter(subtype, filter)
+ else
+ filter = self.search_filter_for_subtype(subtype)
+ end
+ rel = do_search(filter)
+
+ @page_number = [page.to_i, 1].max
+ rel = rel.paginate(:page => @page_number, :per_page => self.class::PER_PAGE)
+
+ rel = self.search_includes(rel, subtype)
+ @page_count = rel.total_pages
+
+ _process_results_page(rel.all)
+ end
+
+ def _add_description(descrip, add)
+ descrip += "; " if 0 < descrip.length
+ descrip + add
+ end
+
+ def description(subtype=TO_JOIN)
+ return '' if self.is_blank?(subtype)
+
+ filter = search_filter_for_subtype(subtype)
+ str = ''
+
+ if filter.has_key?(KEY_SORT_ORDER)
+ str += 'Sort = '
+ case sort = filter[KEY_SORT_ORDER]
+ when 'distance'
+ str += SORT_ORDERS[sort]
+ when 'latency'
+ str += SORT_ORDERS[sort]
+ when 'price_asc'
+ str += HIRE_SORT_ORDERS[sort]
+ when 'price_desc'
+ str += HIRE_SORT_ORDERS[sort]
+ end
+ end
+ if (val = filter[KEY_BAND_TYPE]) != ANY_VAL_STR
+ str = _add_description(str, "Band type = #{BAND_TYPES[val]}")
+ end if filter.has_key?(KEY_BAND_TYPE)
+
+ if (val = filter[KEY_BAND_STATUS]) != ANY_VAL_STR
+ str = _add_description(str, "Band status = #{BAND_STATUS[val]}")
+ end if filter.has_key?(KEY_BAND_STATUS)
+
+ if (val = filter[KEY_PLAY_COMMIT]) != ANY_VAL_STR
+ str = _add_description(str, "Play commitment = #{PLAY_COMMITS[val]}")
+ end if filter.has_key?(KEY_PLAY_COMMIT)
+
+ if (val = filter[KEY_TOUR_OPTION]) != ANY_VAL_STR
+ str = _add_description(str, "Touring options = #{TOUR_OPTIONS[val]}")
+ end if filter.has_key?(KEY_TOUR_OPTION)
+
+ if (val = filter[KEY_PERF_SAMPLES]) != ANY_VAL_STR
+ str = _add_description(str, "Performance samples = #{PERF_SAMPLES[val]}")
+ end if filter.has_key?(KEY_PERF_SAMPLES)
+
+ if (val = filter[KEY_HIRE_MAX_COST].to_i) > 0
+ str = _add_description(str, "Maximum gig cost = $#{val}")
+ end if filter.has_key?(KEY_HIRE_MAX_COST)
+
+ if 0 < filter[KEY_HIRE_FREE]
+ str = _add_description(str, "Bands playing free gigs")
+ end if filter.has_key?(KEY_HIRE_FREE)
+
+ if (val = filter[KEY_GIGS].to_i) != GIG_COUNTS[0]
+ str = _add_description(str, "Concert gigs = #{GIG_LABELS[val]}")
+ end if filter.has_key?(KEY_GIGS)
+
+ if 0 < (val = filter[KEY_GENRES]).length
+ gstr = "Genres = "
+ genres = Genre.where(["id IN (?)", val]).order('description').pluck(:description)
+ gstr += genres.join(', ')
+ str = _add_description(str, gstr)
+ end if filter.has_key?(KEY_GENRES)
+
+ if 0 < ((val = filter[KEY_INSTRUMENTS]) || '').length
+ istr = "Instruments = "
+ instr_ids = val.collect { |vv| vv['instrument_id'] }
+ instrs = Instrument.where(["id IN (?)", instr_ids]).order(:description)
+ instrs.each_with_index do |ii, idx|
+ proficiency = val.detect { |vv| vv['instrument_id'] == ii.id }['proficiency_level']
+ istr += "#{ii.description} (#{INSTRUMENT_PROFICIENCY[proficiency.to_i]})"
+ istr += ', ' unless idx==(instrs.length-1)
+ end
+ str = _add_description(str, istr)
+ end if filter.has_key?(KEY_INSTRUMENTS)
+ str = "Current Search: #{str}"
+ str
+ end
+
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/base_search.rb b/ruby/lib/jam_ruby/models/base_search.rb
new file mode 100644
index 000000000..f08c46a7d
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/base_search.rb
@@ -0,0 +1,196 @@
+module JamRuby
+ class BaseSearch < JsonStore
+
+ attr_accessor :page_count, :results, :page_number
+
+ ANY_VAL_STR = 'any'
+ ANY_VAL_INT = -1
+
+ VAL_YES = 'yes'
+ VAL_NO = 'no'
+
+ PER_PAGE = 10
+ PG_SMALLINT_MAX = 32767
+
+ KEY_SKILL = 'skill_level'
+ KEY_GENRES = 'genres'
+ KEY_INSTRUMENTS = 'instruments'
+ KEY_GIGS = 'concert_gigs'
+ KEY_SORT_ORDER = 'sort_order'
+
+ SORT_VALS = %W{ latency distance }
+ SORT_ORDERS = {
+ SORT_VALS[0] => 'Latency to Me',
+ SORT_VALS[1] => 'Distance to Me'
+ }
+
+ SKILL_VALS = [ANY_VAL_INT, 1, 2]
+ SKILL_LEVELS = {
+ SKILL_VALS[0] => 'Any',
+ SKILL_VALS[1] => 'Amateur',
+ SKILL_VALS[2] => 'Pro',
+ }
+
+ GIG_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4]
+ GIG_LABELS = {
+ GIG_COUNTS[0] => 'Any',
+ GIG_COUNTS[1] => 'under 10',
+ GIG_COUNTS[2] => '10 to 50',
+ GIG_COUNTS[3] => '50 to 100',
+ GIG_COUNTS[4] => 'over 100'
+ }
+
+ INSTRUMENT_PROFICIENCY = {
+ 1 => 'Beginner',
+ 2 => 'Intermediate',
+ 3 => 'Expert',
+ }
+
+ def self.json_schema
+ {
+ KEY_SORT_ORDER => self::SORT_VALS[0],
+ KEY_INSTRUMENTS => [],
+ KEY_GENRES => [],
+ KEY_GIGS => self::GIG_COUNTS[0].to_s,
+ }
+ end
+
+ def self.search_filter_meta(jschema=nil, sort_order=nil)
+ jschema ||= self.json_schema
+ schema_keys = jschema.keys
+ sort_order ||= { keys: self::SORT_VALS, map: self::SORT_ORDERS }
+ multi_keys = jschema.collect { |kk,vv| vv.is_a?(Array) ? kk : nil }.compact
+ {
+ per_page: self::PER_PAGE,
+ filter_keys: {
+ keys: schema_keys,
+ multi: multi_keys,
+ single: schema_keys - multi_keys,
+ },
+ sort_order: sort_order
+ }
+ end
+
+ RESULT_FOLLOW = :follows
+ RESULT_FRIEND = :friends
+
+ COUNT_FRIEND = :count_friend
+ COUNT_FOLLOW = :count_follow
+ COUNT_RECORD = :count_record
+ COUNT_SESSION = :count_session
+ COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION]
+
+ def self.user_search_filter(user)
+ unless ss = user.send(self.name.demodulize.tableize.singularize)
+ ss = self.create_search(user)
+ end
+ ss
+ end
+
+ def self.search_filter_json(user)
+ self.user_search_filter(user).json
+ end
+
+ def self.create_search(user)
+ ms = self.new
+ ms.user = user
+ ms.data_blob = self.json_schema
+ ms.save!
+ ms
+ end
+
+ def self.search_target_class
+ end
+
+ # FIXME: SQL INJECTION
+ def _genres(rel, query_data=json)
+ gids = query_data[KEY_GENRES]
+ unless gids.blank?
+ gidsql = gids.join("','")
+ gpsql = "SELECT player_id FROM genre_players WHERE (player_type = '#{self.class.search_target_class.name}' AND genre_id IN ('#{gidsql}'))"
+ rel = rel.where("#{self.class.search_target_class.table_name}.id IN (#{gpsql})")
+ end
+ rel
+ end
+
+ # FIXME: SQL INJECTION
+ def _instruments(rel, query_data=json)
+ unless (instruments = query_data[KEY_INSTRUMENTS]).blank?
+ instsql = "SELECT player_id FROM musicians_instruments WHERE (("
+ instsql += instruments.collect do |inst|
+ "instrument_id = '#{inst['instrument_id']}' AND proficiency_level = #{inst['proficiency_level']}"
+ end.join(") OR (")
+ instsql += "))"
+ rel = rel.where("#{self.class.search_target_class.table_name}.id IN (#{instsql})")
+ end
+ rel
+ end
+
+ def _gigs(rel)
+ gg = json[KEY_GIGS].to_i
+ rel = rel.where('concert_count = ?',gg) if 0 <= gg
+ rel
+ end
+
+ def _skills(rel)
+ if 0 < (val = json[KEY_SKILL].to_i)
+ rel = rel.where(['skill_level = ?', val])
+ end
+ rel
+ end
+
+ def _sort_order(rel)
+ end
+
+ def do_search(params={})
+ end
+
+ def process_results_page(objs)
+ end
+
+ def search_includes(rel)
+ rel
+ end
+
+ def search_results_page(filter=nil, page=1)
+ if filter
+ self.data_blob = filter
+ self.save
+ else
+ filter = self.data_blob
+ end
+
+ rel = do_search(filter)
+
+ @page_number = [page.to_i, 1].max
+ rel = rel.paginate(:page => @page_number, :per_page => self.class::PER_PAGE)
+
+ rel = self.search_includes(rel)
+ @page_count = rel.total_pages
+
+ process_results_page(rel.all)
+ end
+
+ def reset_filter
+ self.data_blob = self.class.json_schema
+ self.save
+ end
+
+ def reset_search_results
+ reset_filter
+ search_results_page
+ end
+
+ def search_type
+ self.class.to_s
+ end
+
+ def is_blank?
+ self.data_blob == self.class.json_schema
+ end
+
+ def description
+ end
+
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/generic_state.rb b/ruby/lib/jam_ruby/models/generic_state.rb
index 6ed4ec7f4..9d6a23904 100644
--- a/ruby/lib/jam_ruby/models/generic_state.rb
+++ b/ruby/lib/jam_ruby/models/generic_state.rb
@@ -31,6 +31,5 @@ module JamRuby
GenericState.find('default')
end
-
end
end
diff --git a/ruby/lib/jam_ruby/models/genre.rb b/ruby/lib/jam_ruby/models/genre.rb
index 8ae195ffb..b6ab0bd05 100644
--- a/ruby/lib/jam_ruby/models/genre.rb
+++ b/ruby/lib/jam_ruby/models/genre.rb
@@ -19,7 +19,8 @@ module JamRuby
has_and_belongs_to_many :teachers, :class_name => "JamRuby::Teacher", :join_table => "teachers_genres"
# jam tracks
- has_many :jam_tracks, :class_name => "JamRuby::JamTrack"
+ has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "genre_id"
+ has_many :jam_tracks, :through => :genres_jam_tracks, :class_name => "JamRuby::JamTrack", :source => :genre
def to_s
description
diff --git a/ruby/lib/jam_ruby/models/genre_jam_track.rb b/ruby/lib/jam_ruby/models/genre_jam_track.rb
new file mode 100644
index 000000000..aa05e4fd8
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/genre_jam_track.rb
@@ -0,0 +1,8 @@
+module JamRuby
+ class GenreJamTrack < ActiveRecord::Base
+
+ self.table_name = 'genres_jam_tracks'
+ belongs_to :jam_track, class_name: 'JamRuby::JamTrack'
+ belongs_to :genre, class_name: 'JamRuby::Genre'
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb
index 8ab120c62..244cbc6f1 100644
--- a/ruby/lib/jam_ruby/models/jam_track.rb
+++ b/ruby/lib/jam_ruby/models/jam_track.rb
@@ -14,12 +14,12 @@ module JamRuby
attr_accessor :uploading_preview
attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type,
- :original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
+ :original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genres_jam_tracks_attributes, :sales_region, :price,
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
- :jam_track_tap_ins_attributes, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
+ :jam_track_tap_ins_attributes, :genre_ids, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
- validates :name, presence: true, uniqueness: true, length: {maximum: 200}
+ validates :name, presence: true, length: {maximum: 200}
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
validates :description, length: {maximum: 1000}
validates :time_signature, inclusion: {in: [nil] + [''] + TIME_SIGNATURES} # the empty string is needed because of activeadmin
@@ -39,14 +39,17 @@ module JamRuby
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
validates :duration, numericality: {only_integer: true}, :allow_nil => true
- validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,3}$/
- validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,3}$/
+ validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,4}$/, :allow_blank => true
+ validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,4}$/, :allow_blank => true
- belongs_to :genre, class_name: "JamRuby::Genre"
- belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id'
+ belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id', :inverse_of => :jam_tracks
+
+ has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "jam_track_id"
+ has_many :genres, :through => :genres_jam_tracks, :class_name => "JamRuby::Genre", :source => :genre
has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'track_type ASC, position ASC, part ASC, instrument_id ASC'
has_many :jam_track_tap_ins, :class_name => "JamRuby::JamTrackTapIn", order: 'offset_time ASC'
+ has_many :jam_track_files, :class_name => "JamRuby::JamTrackFile"
has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" # '
@@ -67,6 +70,82 @@ module JamRuby
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
+
+ # we can make sure a few things stay in sync here.
+ # 1) the reproduction_royalty_amount has to stay in sync based on duration
+ # 2) the onboarding_exceptions JSON column
+ after_save :sync_reproduction_royalty
+ after_save :sync_onboarding_exceptions
+
+
+ def sync_reproduction_royalty
+
+ # reproduction royalty table based on duration
+
+ # The statutory mechanical royalty rate for permanent digital downloads is:
+ # 9.10¢ per copy for songs 5 minutes or less, or
+ # 1.75¢ per minute or fraction thereof, per copy for songs over 5 minutes.
+ # So the base rate is 9.1 cents for anything up to 5 minutes.
+ # 5.01 to 6 minutes should be 10.5 cents.
+ # 6.01 to 7 minutes should be 12.25 cents.
+ # Etc.
+
+ royalty = nil
+ if self.duration
+ minutes = (self.duration - 1) / 60
+ extra_minutes = minutes - 4
+ extra_minutes = 0 if extra_minutes < 0
+ royalty = (0.091 + (0.0175 * extra_minutes)).round(5)
+ end
+ self.update_column(:reproduction_royalty_amount, royalty)
+
+ true
+ end
+
+ def sync_onboarding_exceptions
+
+ exceptions = {}
+ if self.duration.nil?
+ exceptions[:no_duration] = true
+ end
+
+ if self.genres.count == 0
+ exceptions[:no_genres] = true
+ end
+
+ if self.year.nil?
+ exceptions[:no_year] = true
+ end
+
+ if self.licensor.nil?
+ exceptions[:no_licensor] = true
+ end
+
+ if self.missing_instrument_info?
+ exceptions[:unknown_instrument] = true
+ end
+
+ if self.master_track.nil?
+ exceptions[:no_master] = true
+ end
+
+ if missing_previews?
+ exceptions[:missing_previews] = true
+ end
+
+ if duplicate_positions?
+ exceptions[:duplicate_positions] = true
+ end
+
+ if exceptions.keys.length == 0
+ self.update_column(:onboarding_exceptions, nil)
+ else
+ self.update_column(:onboarding_exceptions, exceptions.to_json)
+ end
+
+ true
+ end
+
def duplicate_positions?
counter = {}
jam_track_tracks.each do |track|
@@ -87,6 +166,17 @@ module JamRuby
duplicate
end
+ def missing_instrument_info?
+ missing_instrument_info = false
+ self.jam_track_tracks.each do |track|
+ if track.instrument_id == 'other' && (track.part == nil || track.part.start_with?('Other'))
+ missing_instrument_info = true
+ break
+ end
+ end
+ missing_instrument_info
+ end
+
def missing_previews?
missing_preview = false
self.jam_track_tracks.each do |track|
@@ -171,7 +261,7 @@ module JamRuby
end
if options[:group_artist]
- query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version, MIN(genre_id) AS genre_id")
+ query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version")
query = query.group("original_artist")
query = query.order('jam_tracks.original_artist')
else
@@ -180,7 +270,12 @@ module JamRuby
end
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
- query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
+
+ unless options[:genre].blank?
+ query = query.joins(:genres)
+ query = query.where('genre_id = ? ', options[:genre])
+ end
+
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type != 'Master'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
@@ -231,7 +326,12 @@ module JamRuby
query = query.order('jam_tracks.original_artist')
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
- query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank?
+
+ unless options[:genre].blank?
+ query = query.joins(:genres)
+ query = query.where('genre_id = ? ', options[:genre])
+ end
+
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
diff --git a/ruby/lib/jam_ruby/models/jam_track_file.rb b/ruby/lib/jam_ruby/models/jam_track_file.rb
new file mode 100644
index 000000000..e7c880165
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/jam_track_file.rb
@@ -0,0 +1,78 @@
+module JamRuby
+
+ # holds a click track or precount file
+ class JamTrackFile < ActiveRecord::Base
+ include JamRuby::S3ManagerMixin
+
+ # there should only be one Master per JamTrack, but there can be N Track per JamTrack
+ FILE_TYPE = %w{ClickWav ClickTxt Precount}
+
+ @@log = Logging.logger[JamTrackFile]
+
+ before_destroy :delete_s3_files
+
+ attr_accessible :jam_track_id, :file_type, :filename, as: :admin
+ attr_accessible :url, :md5, :length, as: :admin
+
+ attr_accessor :original_audio_s3_path, :skip_uploader, :preview_generate_error
+
+ before_destroy :delete_s3_files
+
+ validates :file_type, inclusion: {in: FILE_TYPE }
+
+ belongs_to :jam_track, class_name: "JamRuby::JamTrack"
+
+ # create storage directory that will house this jam_track, as well as
+ def store_dir
+ "jam_track_files"
+ end
+
+ # create name of the file
+ def filename(original_name)
+ "#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
+ end
+
+ def manually_uploaded_filename
+ if click_wav?
+ filename('click.wav')
+ elsif click_txt?
+ filename('click.txt')
+ elsif precount?
+ filename('precount.wav')
+ else
+ raise 'unknown file type: ' + file_type
+ end
+
+ end
+
+ def click_wav?
+ track_type == 'ClickWav'
+ end
+
+ def click_txt?
+ track_type == 'ClickTxt'
+ end
+
+ def precount?
+ track_type == 'Precount'
+ end
+
+ # creates a short-lived URL that has access to the object.
+ # the idea is that this is used when a user who has the rights to this tries to download this JamTrack
+ # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
+ # but the url is short lived enough so that it wouldn't be easily shared
+ def sign_url(expiration_time = 120)
+ s3_manager.sign_url(self[url], {:expires => expiration_time, :response_content_type => 'audio/wav', :secure => true})
+ end
+
+ def can_download?(user)
+ # I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download?
+ jam_track.owners.include?(user)
+ end
+
+
+ def delete_s3_files
+ s3_manager.delete(self[:url]) if self[:url] && s3_manager.exists?(self[:url])
+ end
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/jam_track_licensor.rb b/ruby/lib/jam_ruby/models/jam_track_licensor.rb
index d5ee3df75..e06e292bc 100644
--- a/ruby/lib/jam_ruby/models/jam_track_licensor.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_licensor.rb
@@ -1,6 +1,8 @@
module JamRuby
class JamTrackLicensor < ActiveRecord::Base
+ table_name = 'jam_track_licensors'
+
attr_accessible :name, :description, :attention, :address_line_1, :address_line_2,
:city, :state, :zip_code, :contact, :email, :phone, as: :admin
@@ -16,6 +18,6 @@ module JamRuby
validates :email, length: {maximum: 200}
validates :phone, length: {maximum: 200}
- has_many :jam_tracks, :class_name => "JamRuby::JamTrack", foreign_key: 'licensor_id'
+ has_many :jam_tracks, :class_name => "JamRuby::JamTrack", foreign_key: 'licensor_id', :inverse_of => :licensor
end
end
diff --git a/ruby/lib/jam_ruby/models/jam_track_track.rb b/ruby/lib/jam_ruby/models/jam_track_track.rb
index e20469076..c0548e6fd 100644
--- a/ruby/lib/jam_ruby/models/jam_track_track.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_track.rb
@@ -24,7 +24,7 @@ module JamRuby
before_destroy :delete_s3_files
validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000}
- validates :part, length: {maximum: 25}
+ validates :part, length: {maximum: 35}
validates :track_type, inclusion: {in: TRACK_TYPE }
validates :preview_start_time, numericality: {only_integer: true}, length: {in: 1..1000}, :allow_nil => true
validates_uniqueness_of :part, scope: [:jam_track_id, :instrument_id]
@@ -131,81 +131,19 @@ module JamRuby
end
+
def generate_preview
begin
Dir.mktmpdir do |tmp_dir|
input = File.join(tmp_dir, 'in.ogg')
- output = File.join(tmp_dir, 'out.ogg')
- output_mp3 = File.join(tmp_dir, 'out.mp3')
-
- start = self.preview_start_time.to_f / 1000
- stop = start + 20
raise 'no track' unless self["url_44"]
s3_manager.download(self.url_by_sample_rate(44), input)
- command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
-
- @@log.debug("trimming using: " + command)
-
- sox_output = `#{command}`
-
- result_code = $?.to_i
-
- if result_code != 0
- @@log.debug("fail #{result_code}")
- @preview_generate_error = "unable to execute cut command #{sox_output}"
- else
- # now create mp3 off of ogg preview
-
- convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\""
-
- @@log.debug("converting to mp3 using: " + convert_mp3_cmd)
-
- convert_output = `#{convert_mp3_cmd}`
-
- result_code = $?.to_i
-
- if result_code != 0
- @@log.debug("fail #{result_code}")
- @preview_generate_error = "unable to execute mp3 convert command #{convert_output}"
- else
- ogg_digest = ::Digest::MD5.file(output)
- mp3_digest = ::Digest::MD5.file(output_mp3)
- self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest
- self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest
-
- @@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}")
- s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest)
- @@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}")
- s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest)
-
- self.skip_uploader = true
-
- original_ogg_preview_url = self["preview_url"]
- original_mp3_preview_url = self["preview_mp3_url"]
-
- # and finally update the JamTrackTrack with the new info
- self["preview_url"] = self.preview_filename(ogg_md5, 'ogg')
- self["preview_length"] = File.new(output).size
- # and finally update the JamTrackTrack with the new info
- self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3')
- self["preview_mp3_length"] = File.new(output_mp3).size
- self.save!
-
- # if all that worked, now delete old previews, if present
- begin
- s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"]
- s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"]
- rescue
- puts "UNABLE TO CLEANUP OLD PREVIEW URL"
- end
-
- end
- end
+ process_preview(input, tmp_dir)
end
rescue Exception => e
@@log.error("error in sox command #{e.to_s}")
@@ -214,6 +152,76 @@ module JamRuby
end
+ # input is the original ogg file for the track. tmp_dir is where this code can safely generate output stuff and have it cleaned up later
+ def process_preview(input, tmp_dir)
+ uuid = SecureRandom.uuid
+ output = File.join(tmp_dir, "#{uuid}.ogg")
+ output_mp3 = File.join(tmp_dir, "#{uuid}.mp3")
+
+ start = self.preview_start_time.to_f / 1000
+ stop = start + 20
+
+ command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
+
+ @@log.debug("trimming using: " + command)
+
+ sox_output = `#{command}`
+
+ result_code = $?.to_i
+
+ if result_code != 0
+ @@log.debug("fail #{result_code}")
+ @preview_generate_error = "unable to execute cut command #{sox_output}"
+ else
+ # now create mp3 off of ogg preview
+
+ convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\""
+
+ @@log.debug("converting to mp3 using: " + convert_mp3_cmd)
+
+ convert_output = `#{convert_mp3_cmd}`
+
+ result_code = $?.to_i
+
+ if result_code != 0
+ @@log.debug("fail #{result_code}")
+ @preview_generate_error = "unable to execute mp3 convert command #{convert_output}"
+ else
+ ogg_digest = ::Digest::MD5.file(output)
+ mp3_digest = ::Digest::MD5.file(output_mp3)
+ self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest
+ self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest
+
+ @@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}")
+ s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest)
+ @@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}")
+ s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest)
+
+ self.skip_uploader = true
+
+ original_ogg_preview_url = self["preview_url"]
+ original_mp3_preview_url = self["preview_mp3_url"]
+
+ # and finally update the JamTrackTrack with the new info
+ self["preview_url"] = self.preview_filename(ogg_md5, 'ogg')
+ self["preview_length"] = File.new(output).size
+ # and finally update the JamTrackTrack with the new info
+ self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3')
+ self["preview_mp3_length"] = File.new(output_mp3).size
+ self.save!
+
+ # if all that worked, now delete old previews, if present
+ begin
+ s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"]
+ s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"]
+ rescue
+ puts "UNABLE TO CLEANUP OLD PREVIEW URL"
+ end
+
+ end
+ end
+ end
+
private
def normalize_position
diff --git a/ruby/lib/jam_ruby/models/musician_search.rb b/ruby/lib/jam_ruby/models/musician_search.rb
index 9b780c53f..f11e36e20 100644
--- a/ruby/lib/jam_ruby/models/musician_search.rb
+++ b/ruby/lib/jam_ruby/models/musician_search.rb
@@ -1,22 +1,12 @@
module JamRuby
- class MusicianSearch < JsonStore
+ class MusicianSearch < BaseSearch
- attr_accessor :page_count, :results, :user_counters, :page_number
+ cattr_accessor :jschema, :search_meta
+ attr_accessor :user_counters
- ANY_VAL_STR = 'any'
- ANY_VAL_INT = -1
-
- PER_PAGE = 10
- PG_SMALLINT_MAX = 32767
-
- KEY_GIGS = 'concert_gigs'
KEY_STUDIOS = 'studio_sessions'
KEY_AGES = 'ages'
- KEY_SKILL = 'skill_level'
- KEY_GENRES = 'genres'
- KEY_INSTRUMENTS = 'instruments'
KEY_INTERESTS = 'interests'
- KEY_SORT_ORDER = 'sort_order'
SORT_VALS = %W{ latency distance }
SORT_ORDERS = {
@@ -24,22 +14,6 @@ module JamRuby
SORT_VALS[1] => 'Distance to Me'
}
- SKILL_VALS = [ANY_VAL_INT, 1, 2]
- SKILL_LEVELS = {
- SKILL_VALS[0] => 'Any',
- SKILL_VALS[1] => 'Amateur',
- SKILL_VALS[2] => 'Pro',
- }
-
- GIG_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4]
- GIG_LABELS = {
- GIG_COUNTS[0] => 'Any',
- GIG_COUNTS[1] => 'under 10',
- GIG_COUNTS[2] => '10 to 50',
- GIG_COUNTS[3] => '50 to 100',
- GIG_COUNTS[4] => 'over 100'
- }
-
STUDIO_COUNTS = [ANY_VAL_INT, 0, 1, 2, 3, 4]
STUDIOS_LABELS = {
STUDIO_COUNTS[0] => 'Any',
@@ -74,81 +48,28 @@ module JamRuby
INTEREST_VALS[5] => 'Co-Writing'
}
- INSTRUMENT_PROFICIENCY = {
- 1 => 'Beginner',
- 2 => 'Intermediate',
- 3 => 'Expert',
- }
-
- JSON_SCHEMA = {
- KEY_SORT_ORDER => SORT_VALS[0],
- KEY_INSTRUMENTS => [],
- KEY_INTERESTS => INTEREST_VALS[0],
- KEY_GENRES => [],
- KEY_GIGS => GIG_COUNTS[0].to_s,
- KEY_STUDIOS => STUDIO_COUNTS[0].to_s,
- KEY_SKILL => SKILL_VALS[0].to_s,
- KEY_AGES => []
- }
- JSON_SCHEMA_KEYS = JSON_SCHEMA.keys
- MULTI_VALUE_KEYS = JSON_SCHEMA.collect { |kk,vv| vv.is_a?(Array) ? kk : nil }.compact
- SINGLE_VALUE_KEYS = JSON_SCHEMA.keys - MULTI_VALUE_KEYS
-
- SEARCH_FILTER_META = {
- per_page: PER_PAGE,
- filter_keys: {
- keys: JSON_SCHEMA_KEYS,
- multi: MULTI_VALUE_KEYS,
- single: SINGLE_VALUE_KEYS,
- },
- sort_order: { keys: SORT_VALS, map: SORT_ORDERS },
- interests: { keys: INTEREST_VALS, map: INTERESTS },
- ages: { keys: AGE_COUNTS, map: AGES }
- }
-
- def self.user_search_filter(user)
- unless ms = user.musician_search
- ms = self.create_search(user)
- end
- ms
+ def self.json_schema
+ return @@jschema if @@jschema
+ @@jschema = BaseSearch.json_schema.merge({
+ KEY_INTERESTS => INTEREST_VALS[0],
+ KEY_STUDIOS => STUDIO_COUNTS[0].to_s,
+ KEY_AGES => [],
+ KEY_SKILL => self::SKILL_VALS[0].to_s,
+ })
end
- def self.search_filter_json(user)
- self.user_search_filter(user).json
+ def self.search_filter_meta
+ return @@search_meta if @@search_meta
+ @@search_meta = super.merge({
+ interests: { keys: INTEREST_VALS, map: INTERESTS },
+ ages: { keys: AGE_COUNTS, map: AGES }
+ })
end
- def self.create_search(user)
- ms = self.new
- ms.user = user
- ms.data_blob = JSON_SCHEMA
- ms.save!
- ms
+ def self.search_target_class
+ User
end
- # XXX SQL INJECTION
- def _genres(rel)
- gids = json[KEY_GENRES]
- unless gids.blank?
- gidsql = gids.join("','")
- gpsql = "SELECT player_id FROM genre_players WHERE (player_type = 'JamRuby::User' AND genre_id IN ('#{gidsql}'))"
- rel = rel.where("users.id IN (#{gpsql})")
- end
- rel
- end
-
- # XXX SQL INJECTION
- def _instruments(rel)
- unless (instruments = json['instruments']).blank?
- instsql = "SELECT player_id FROM musicians_instruments WHERE (("
- instsql += instruments.collect do |inst|
- "instrument_id = '#{inst['id']}' AND proficiency_level = #{inst['level']}"
- end.join(") OR (")
- instsql += "))"
- rel = rel.where("users.id IN (#{instsql})")
- end
- rel
- end
-
def _ages(rel)
unless (vals = json[KEY_AGES]).blank?
return rel if vals.detect { |vv| ANY_VAL_INT == vv }
@@ -193,7 +114,7 @@ module JamRuby
def _skills(rel)
if 0 < (val = json[KEY_SKILL].to_i)
- rel = rel.where(['skill_level = ?', val])
+ rel = rel.where(skill_level: val)
end
rel
end
@@ -207,8 +128,8 @@ module JamRuby
end
def _sort_order(rel)
- val = json[KEY_SORT_ORDER]
- if SORT_VALS[1] == val
+ val = json[self.class::KEY_SORT_ORDER]
+ if self.class::SORT_VALS[1] == val
locidispid = self.user.last_jam_locidispid || 0
my_locid = locidispid / 1000000
rel = rel.joins("LEFT JOIN geoiplocations AS my_geo ON my_geo.locid = #{my_locid}")
@@ -235,45 +156,11 @@ module JamRuby
rel
end
- def search_results_page(filter=nil, page=1)
- if filter
- self.data_blob = filter
- self.save
- else
- filter = self.data_blob
- end
-
- rel = do_search(filter)
-
- @page_number = [page.to_i, 1].max
- rel = rel.paginate(:page => @page_number, :per_page => PER_PAGE)
-
- rel = rel.includes([:instruments, :followings, :friends])
- @page_count = rel.total_pages
-
- musician_results(rel.all)
- end
-
- def reset_filter
- self.data_blob = JSON_SCHEMA
- self.save
+ def search_includes(rel)
+ rel.includes([:instruments, :followings, :friends])
end
- def reset_search_results
- reset_filter
- search_results_page
- end
-
- RESULT_FOLLOW = :follows
- RESULT_FRIEND = :friends
-
- COUNT_FRIEND = :count_friend
- COUNT_FOLLOW = :count_follow
- COUNT_RECORD = :count_record
- COUNT_SESSION = :count_session
- COUNTERS = [COUNT_FRIEND, COUNT_FOLLOW, COUNT_RECORD, COUNT_SESSION]
-
- def musician_results(_results)
+ def process_results_page(_results)
@results = _results
@user_counters = {} and return self unless user
@@ -351,7 +238,7 @@ module JamRuby
end
def is_blank?
- self.data_blob == JSON_SCHEMA
+ self.data_blob == self.class.json_schema
end
def description
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index a7ae1b124..0e8afac88 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -173,6 +173,7 @@ module JamRuby
has_many :performance_samples, :class_name => "JamRuby::PerformanceSample", :foreign_key=> 'player_id'
has_one :musician_search, :class_name => 'JamRuby::MusicianSearch'
+ has_one :band_search, :class_name => 'JamRuby::BandSearch'
before_save :create_remember_token, :if => :should_validate_password?
before_save :stringify_avatar_info , :if => :updating_avatar
diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb
index 626b906fb..9888bc8aa 100644
--- a/ruby/spec/factories.rb
+++ b/ruby/spec/factories.rb
@@ -750,7 +750,7 @@ FactoryGirl.define do
licensor_royalty_amount 0.999
sequence(:plan_code) { |n| "jamtrack-#{n}" }
- genre JamRuby::Genre.first
+ genres [JamRuby::Genre.first]
association :licensor, factory: :jam_track_licensor
factory :jam_track_with_tracks do
diff --git a/ruby/spec/jam_ruby/jam_track_importer_spec.rb b/ruby/spec/jam_ruby/jam_track_importer_spec.rb
index a2cf96620..fbdc3d9cc 100644
--- a/ruby/spec/jam_ruby/jam_track_importer_spec.rb
+++ b/ruby/spec/jam_ruby/jam_track_importer_spec.rb
@@ -22,11 +22,13 @@ describe JamTrackImporter do
in_directory_with_file(metafile)
before(:each) do
+ JamTrackImporter.storage_format = 'default'
content_for_file(YAML.dump(sample_yml))
end
it "no meta" do
s3_metalocation = 'audio/Artist 1/Bogus Place/meta.yml'
+ JamTrackImporter.storage_format = 'default'
JamTrackImporter.load_metalocation(s3_metalocation).should be_nil
end
@@ -38,9 +40,105 @@ describe JamTrackImporter do
end
end
+ describe "sort_tracks" do
+ let(:jam_track) { FactoryGirl.create(:jam_track) }
+ let(:importer) { JamTrackImporter.new() }
+ let(:vocal) {Instrument.find('voice')}
+ let(:drums) {Instrument.find('drums')}
+ let(:bass_guitar) {Instrument.find('bass guitar')}
+ let(:piano) {Instrument.find('piano')}
+ let(:keyboard) {Instrument.find('keyboard')}
+ let(:acoustic_guitar) {Instrument.find('acoustic guitar')}
+ let(:electric_guitar) {Instrument.find('electric guitar')}
+ let(:other) {Instrument.find('other')}
+
+ it "the big sort" do
+ # specified in https://jamkazam.atlassian.net/browse/VRFS-3296
+ vocal_lead = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: vocal, part: 'Lead')
+ vocal_lead_female = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: vocal, part: 'Lead Female')
+ vocal_lead_male = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: vocal, part: 'Lead Male')
+ vocal_backing = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: vocal, part: 'Backing')
+ vocal_random = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: vocal, part: 'Random')
+ drums_drums = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: drums, part: 'Drums')
+ drums_percussion = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: drums, part: 'Percussion')
+ drums_random_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: drums, part: 'A')
+ drums_random_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: drums, part: 'C')
+ bass_guitar_bass = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: bass_guitar, part: 'Bass')
+ bass_guitar_random_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: bass_guitar, part: 'some bass')
+ bass_guitar_random_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: bass_guitar, part: 'zome bass')
+ piano_piano = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: piano, part: 'Piano')
+ keyboard_synth_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: keyboard, part: 'Synth 1')
+ keyboard_synth_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: keyboard, part: 'Synth 2')
+ keyboard_pads = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: keyboard, part: 'Pads')
+ keyboard_random_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: keyboard, part: 'A')
+ keyboard_random_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: keyboard, part: 'Z')
+ acoust_guitar_lead = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'Lead')
+ acoust_guitar_lead_x = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'Lead X')
+ acoust_guitar_solo_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'Solo 1')
+ acoust_guitar_solo_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'Solo 2')
+ acoust_guitar_rhythm = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'Rhythm')
+ acoust_guitar_random_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'A')
+ acoust_guitar_random_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: acoustic_guitar, part: 'Z')
+ elect_guitar_lead = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'Lead')
+ elect_guitar_lead_x = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'Lead X')
+ elect_guitar_solo_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'Solo 1')
+ elect_guitar_solo_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'Solo 2')
+ elect_guitar_rhythm = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'Rhythm')
+ elect_guitar_random_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'A')
+ elect_guitar_random_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: electric_guitar, part: 'Z')
+ other_1 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: other, part: 'Other 1')
+ other_2 = FactoryGirl.build(:jam_track_track, jam_track: jam_track, instrument: other, part: 'Other 2')
+
+ expected = [
+ vocal_lead,
+ vocal_lead_female,
+ vocal_lead_male,
+ vocal_backing,
+ vocal_random,
+ drums_drums,
+ drums_percussion,
+ drums_random_1,
+ drums_random_2,
+ bass_guitar_bass,
+ piano_piano,
+ keyboard_synth_1,
+ keyboard_synth_2,
+ keyboard_pads,
+ keyboard_random_1,
+ keyboard_random_2,
+ acoust_guitar_lead,
+ acoust_guitar_lead_x,
+ acoust_guitar_rhythm,
+ acoust_guitar_random_1,
+ acoust_guitar_solo_1,
+ acoust_guitar_solo_2,
+ acoust_guitar_random_2,
+ elect_guitar_lead,
+ elect_guitar_lead_x,
+ elect_guitar_solo_1,
+ elect_guitar_solo_2,
+ elect_guitar_rhythm,
+ elect_guitar_random_1,
+ elect_guitar_random_2,
+ bass_guitar_random_1,
+ bass_guitar_random_2,
+ other_1,
+ other_2
+ ]
+ shuffled = expected.shuffle
+ sorted_tracks = importer.sort_tracks(shuffled)
+
+ importer.set_custom_weight(vocal_lead).should eq(100)
+
+ expected.each_with_index do |expected_track, i|
+ sorted_tracks[i].should eq(expected_track)
+ end
+ end
+ end
+
describe "synchronize" do
let(:jam_track) { JamTrack.new }
- let(:importer) { JamTrackImporter.new }
+ let(:importer) { JamTrackImporter.new() }
let(:minimum_meta) { nil }
let(:metalocation) { 'audio/Artist 1/Song 1/meta.yml' }
let(:options) {{ skip_audio_upload:true }}
@@ -64,7 +162,7 @@ describe JamTrackImporter do
describe "parse_wav" do
it "Guitar" do
- result = JamTrackImporter.new.parse_wav('blah/Ready for Love Stem - Guitar - Main.wav')
+ result = JamTrackImporter.new.parse_file('blah/Ready for Love Stem - Guitar - Main.wav')
result[:instrument].should eq('electric guitar')
result[:part].should eq('Main')
end
diff --git a/ruby/spec/jam_ruby/models/band_filter_search_spec.rb b/ruby/spec/jam_ruby/models/band_filter_search_spec.rb
index 1805a0873..bc2c0c206 100644
--- a/ruby/spec/jam_ruby/models/band_filter_search_spec.rb
+++ b/ruby/spec/jam_ruby/models/band_filter_search_spec.rb
@@ -1,6 +1,19 @@
require 'spec_helper'
-describe 'Band search' do
+describe 'Band Search Model' do
+
+ let!(:searcher) { FactoryGirl.create(:austin_user) }
+ let!(:search) { BandSearch.user_search_filter(searcher) }
+
+ let!(:austin_user) { FactoryGirl.create(:austin_user) }
+ let!(:dallas_user) { FactoryGirl.create(:dallas_user) }
+ let!(:miami_user) { FactoryGirl.create(:miami_user) }
+ let!(:seattle_user) { FactoryGirl.create(:seattle_user) }
+
+ let!(:user_types) { [:austin_user, :dallas_user, :miami_user, :seattle_user] }
+
+ let!(:to_join) { search.search_filter_for_subtype(BandSearch::TO_JOIN) }
+ let!(:to_hire) { search.search_filter_for_subtype(BandSearch::TO_HIRE) }
before(:all) do
Recording.delete_all
@@ -20,213 +33,310 @@ describe 'Band search' do
FactoryGirl.create(:band_musician, :band => bb, :user => FactoryGirl.create(:user))
end
end
+ end
+
+ describe "creates search obj" do
+
+ before(:all) do
+ User.delete_all
+ end
+
+ it "associates to user" do
+ expect(search.user).to eq(searcher)
+ searcher.reload
+ expect(searcher.band_search).to eq(search)
+ end
+
+ it "sets json" do
+ expect(search.search_filter_for_subtype(BandSearch::TO_JOIN)).to eq(BandSearch.json_schema[BandSearch::TO_JOIN])
+ expect(search.search_filter_for_subtype(BandSearch::TO_HIRE)).to eq(BandSearch.json_schema[BandSearch::TO_HIRE])
+ end
+
+ it "loads all bands by default" do
+ search.search_results_page
+ expect(search.results.count).to eq(Band.count)
+ end
+
+ it "has follower counts" do
+ Follow.create(user_id: searcher.id, followable: Band.first)
+ search.search_results_page
+ expect(search.follow_count(Band.first)).to eq(1)
+ end
end
- context 'default filter settings' do
+ describe "generates description" do
- it "finds all bands" do
- # expects all the bands
- num = Band.count
- results = Search.band_filter({ :per_page => num })
- expect(results.results.count).to eq(num)
+ def check_description(_filter, subtype, key, value, lookup, label)
+ _filter[key] = value
+ search.search_results_page(subtype, _filter)
+ expect(search.description(subtype)).to match(/Current Search: Sort = .*; #{label} = #{lookup[value]}$/)
end
- it "finds bands with proper ordering" do
- # the ordering should be create_at since no followers exist
- expect(Follow.count).to eq(0)
- results = Search.band_filter({ :per_page => Band.count })
+ it "renders no description for blank" do
+ search.search_results_page
+ expect(search.description).to eq('')
+ end
+
+ it "renders description for sort order" do
+ to_join[BandSearch::KEY_TOUR_OPTION] = BandSearch::VAL_YES
+ search.search_results_page(BandSearch::TO_JOIN, to_join)
+ search.search_results_page
+ expect(search.description).to match(/Sort =/)
+ end
+
+ context "to_join" do
+
+ it "renders description for band type" do
+ check_description(to_join,
+ BandSearch::TO_JOIN,
+ BandSearch::KEY_BAND_TYPE,
+ BandSearch::BAND_TYPE_VAL_STRS[1],
+ BandSearch::BAND_TYPES,
+ 'Band type')
+ end
+
+ it "renders description for band status" do
+ check_description(to_join,
+ BandSearch::TO_JOIN,
+ BandSearch::KEY_BAND_STATUS,
+ BandSearch::SKILL_VALS[1],
+ BandSearch::BAND_STATUS,
+ 'Band status')
+ end
+
+ it "renders description for concert gigs" do
+ check_description(to_join,
+ BandSearch::TO_JOIN,
+ BandSearch::KEY_GIGS,
+ BandSearch::GIG_COUNTS[1],
+ BandSearch::GIG_LABELS,
+ 'Concert gigs')
+ end
+
+ it "renders description for play commitment" do
+ check_description(to_join,
+ BandSearch::TO_JOIN,
+ BandSearch::KEY_PLAY_COMMIT,
+ BandSearch::PLAY_COMMIT_VALS[1],
+ BandSearch::PLAY_COMMITS,
+ 'Play commitment')
+ end
+
+ it "renders description for tour option" do
+ check_description(to_join,
+ BandSearch::TO_JOIN,
+ BandSearch::KEY_TOUR_OPTION,
+ BandSearch::VAL_YES,
+ BandSearch::TOUR_OPTIONS,
+ 'Touring options')
+ end
+
+ it "renders description for genres" do
+ to_join[BandSearch::KEY_GENRES] = [Genre.first.id]
+ search.search_results_page(BandSearch::TO_JOIN, to_join)
+ expect(search.description).to match(/Current Search: Sort = .*; Genres = #{Genre.first.description}$/)
+ end
+
+ it "renders description for instruments" do
+ to_join[BandSearch::KEY_INSTRUMENTS] = [{ 'instrument_id' => Instrument.first.id, 'proficiency_level' => 1 }]
+ search.search_results_page(BandSearch::TO_JOIN, to_join)
+ expect(search.description).to match(/Current Search: Sort = .*; Instruments = #{Instrument.first.description} \(#{BandSearch::INSTRUMENT_PROFICIENCY[1]}\)$/)
+ end
+
+ end
+
+ context "to_hire" do
+ it "renders description for genres" do
+ to_hire[BandSearch::KEY_GENRES] = [Genre.first.id]
+ search.search_results_page(BandSearch::TO_HIRE, to_hire)
+ expect(search.description(BandSearch::TO_HIRE)).to match(/Current Search: Sort = .*; Genres = #{Genre.first.description}$/)
+ end
+
+ it "renders description for band status" do
+ check_description(to_hire,
+ BandSearch::TO_HIRE,
+ BandSearch::KEY_BAND_STATUS,
+ BandSearch::BAND_STATUS_VALS[1],
+ BandSearch::BAND_STATUS,
+ 'Band status')
+ end
+
+ it "renders description for concert gigs" do
+ check_description(to_hire,
+ BandSearch::TO_HIRE,
+ BandSearch::KEY_GIGS,
+ BandSearch::GIG_COUNTS[1],
+ BandSearch::GIG_LABELS,
+ 'Concert gigs')
+ end
+
+ it "renders description for performance samples" do
+ check_description(to_hire,
+ BandSearch::TO_HIRE,
+ BandSearch::KEY_PERF_SAMPLES,
+ BandSearch::PERF_SAMPLES_VALS[1],
+ BandSearch::PERF_SAMPLES,
+ 'Performance samples')
+ end
+
+ it "renders description max cost" do
+ to_hire[BandSearch::KEY_HIRE_MAX_COST] = 100
+ search.search_results_page(BandSearch::TO_HIRE, to_hire)
+ expect(search.description(BandSearch::TO_HIRE)).to match(/Current Search: Sort = .*; Maximum gig cost = \$100$/)
+ end
+
+ it "renders description free gigs" do
+ to_hire[BandSearch::KEY_HIRE_FREE] = 1
+ search.search_results_page(BandSearch::TO_HIRE, to_hire)
+ expect(search.description(BandSearch::TO_HIRE)).to match(/Current Search: Sort = .*; Bands playing free gigs$/)
+ end
+
+ end
+
+ end
+
+ describe "filtering by keys" do
+
+ let!(:band) { Band.first }
+
+ context 'all search keys' do
+
+ let!(:filter) { to_join }
+
+ it "filters by gigs" do
+ band.update_attribute(:concert_count, BandSearch::GIG_COUNTS[2])
+ filter[BandSearch::KEY_GIGS] = BandSearch::GIG_COUNTS[2]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
+ end
+ it "filters by genre" do
+ band_id = band.id
+ filter[BandSearch::KEY_GENRES] = [band_id]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(Band.all.map(&:genres).flatten.select { |bb| bb.id == band_id }.count)
+ end
+
+ it "filters by band_type" do
+ band.update_attribute(:band_type, BandSearch::BAND_TYPE_VAL_STRS[1])
+ filter[BandSearch::KEY_BAND_TYPE] = BandSearch::BAND_TYPE_VAL_STRS[1]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
+ end
+
+ it "filters by instruments" do
+ minst = FactoryGirl.create(:musician_instrument)
+ band.musician_instruments << minst
+ band.save
+ filter[BandSearch::KEY_INSTRUMENTS] = [{'instrument_id' => minst.instrument_id,
+ 'proficiency_level' => minst.proficiency_level
+ }]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
+
+ filter[BandSearch::KEY_INSTRUMENTS] = [{'instrument_id' => minst.instrument_id,
+ 'proficiency_level' => minst.proficiency_level + 1
+ }]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(0)
+ end
+
+ end
+
+ context 'to_join' do
+
+ let!(:filter) { to_join }
+
+ it "sorts by distance" do
+ bands = Band.all.reverse
+ bb = bands.first
+ bb.lat, bb.lng = austin_geoip[:geoiplocation].latitude, austin_geoip[:geoiplocation].longitude
+ bb.save!
+ bb = bands.second
+ bb.lat, bb.lng = dallas_geoip[:geoiplocation].latitude, dallas_geoip[:geoiplocation].longitude
+ bb.save!
+ bb = bands.third
+ bb.lat, bb.lng = miami_geoip[:geoiplocation].latitude, miami_geoip[:geoiplocation].longitude
+ bb.save!
+ bb = bands.fourth
+ bb.lat, bb.lng = seattle_geoip[:geoiplocation].latitude, seattle_geoip[:geoiplocation].longitude
+ bb.save!
+
+ filter[BandSearch::KEY_SORT_ORDER] = BandSearch::SORT_VALS[0]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(Band.count)
+ expect(search.results.first.id).to eq(bands.first.id)
+ expect(search.results.second.id).to eq(bands.second.id)
+ expect(search.results.third.id).to eq(bands.third.id)
+ expect(search.results.fourth.id).to eq(bands.fourth.id)
+ end
+
+ it "filters by play commitment" do
+ band.update_attribute(BandSearch::KEY_PLAY_COMMIT, BandSearch::PLAY_COMMIT_VALS[1].to_i)
+ filter[BandSearch::KEY_PLAY_COMMIT] = BandSearch::PLAY_COMMIT_VALS[1]
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
+ end
+
+ it "filters by tour option" do
+ band.update_attribute(BandSearch::KEY_TOUR_OPTION, true)
+ filter[BandSearch::KEY_TOUR_OPTION] = BandSearch::VAL_YES
+ search.search_results_page(BandSearch::TO_JOIN, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
+ end
+
+ end
+
+
+ context 'to_hire' do
- rbands = @bands.reverse
- results.results.each_with_index do |uu, idx|
- expect(uu.id).to eq(@bands.reverse[idx].id)
- end
- end
+ let!(:filter) { to_hire }
- it "sorts bands by followers" do
- users = []
- 4.downto(1) { |nn| users << FactoryGirl.create(:user) }
-
- users.each_with_index do |u, index|
- if index != 0
- f1 = Follow.new
- f1.user = u
- f1.followable = @band4
- f1.save
- end
+ it "filters by free gigs" do
+ band.update_attribute(BandSearch::KEY_HIRE_FREE, true)
+ filter[BandSearch::KEY_HIRE_FREE] = BandSearch::VAL_YES
+ search.search_results_page(BandSearch::TO_HIRE, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
end
- users.each_with_index do |u, index|
- if index != 0
- f1 = Follow.new
- f1.user = u
- f1.followable = @band3
- f1.save
- end
+ it "filters by max cost" do
+ band.update_attribute(:gig_minimum, 10)
+ filter[BandSearch::KEY_HIRE_MAX_COST] = 5
+ search.search_results_page(BandSearch::TO_HIRE, filter)
+ expect(search.results.count).to eq(0)
+
+ filter[BandSearch::KEY_HIRE_MAX_COST] = 15
+ search.search_results_page(BandSearch::TO_HIRE, filter)
+ expect(search.results.count).to eq(1)
end
- f1 = Follow.new
- f1.user = users.first
- f1.followable = @band2
- f1.save
+ it "filters by perform samples" do
+ filter[BandSearch::KEY_PERF_SAMPLES] = BandSearch::VAL_YES
+ search.search_results_page(BandSearch::TO_HIRE, filter)
+ expect(search.results.count).to eq(0)
- # establish sorting order
- # @band4.followers.concat(users[1..-1])
- # @band3.followers.concat(users[1..3])
- # @band2.followers.concat(users[0])
- @bands.map(&:reload)
+ filter[BandSearch::KEY_PERF_SAMPLES] = BandSearch::VAL_NO
+ search.search_results_page(BandSearch::TO_HIRE, filter)
+ expect(search.results.count).to eq(Band.count)
- expect(@band4.followers.count).to be 3
- expect(Follow.count).to be 7
-
- # refresh the order to ensure it works right
- users.each_with_index do |u, index|
- if index != 0
- f1 = Follow.new
- f1.user = u
- f1.followable = @band2
- f1.save
- end
+ ps = PerformanceSample.new
+ ps.player = band
+ ps.service_type = 'youtube'
+ ps.service_id = 'abc123'
+ ps.save
+ # PerformanceSample.create(player: band, service_type: 'youtube', service_id: 'abc123')
+ filter[BandSearch::KEY_PERF_SAMPLES] = BandSearch::VAL_YES
+ search.search_results_page(BandSearch::TO_HIRE, filter)
+ expect(search.results.count).to eq(1)
+ expect(search.results[0].id).to eq(band.id)
end
- # @band2.followers.concat(users[1..-1])
- results = Search.band_filter({ :per_page => @bands.size }, users[0])
- expect(results.results[0].id).to eq(@band2.id)
-
- # check the follower count for given entry
- expect(results.results[0].search_follow_count.to_i).not_to eq(0)
- # check the follow relationship between current_user and result
- expect(results.is_follower?(@band2)).to be true
- end
-
- it 'paginates properly' do
- # make sure pagination works right
- params = { :per_page => 2, :page => 1 }
- results = Search.band_filter(params)
- expect(results.results.count).to be 2
- end
-
- end
-
- def make_session(band)
- usr = band.users[0]
- session = FactoryGirl.create(:active_music_session, :creator => usr, :description => "Session", :band => band)
- FactoryGirl.create(:connection, :user => usr, :music_session => session)
- user = FactoryGirl.create(:user)
- session
- end
-
- context 'band stat counters' do
-
- it "follow stat shows follower count" do
- users = []
- 2.downto(1) { |nn| users << FactoryGirl.create(:user) }
-
- users.each do |u|
- f1 = Follow.new
- f1.user = u
- f1.followable = @band1
- f1.save
- end
-
- # establish sorting order
- # @band1.followers.concat(users)
- results = Search.band_filter({},@band1)
- uu = results.results.detect { |mm| mm.id == @band1.id }
- expect(uu).to_not be_nil
- expect(results.follow_count(uu)).to eq(users.count)
- end
-
- it "session stat shows session count" do
- make_session(@band1)
- @band1.reload
- results = Search.band_filter({},@band1)
- uu = results.results.detect { |mm| mm.id == @band1.id }
- expect(uu).to_not be_nil
- expect(results.session_count(uu)).to be 1
- end
-
- end
-
- context 'band sorting' do
-
- it "by plays" do
- make_session(@band2)
- make_session(@band2)
- make_session(@band2)
- make_session(@band1)
- # order results by num recordings
- results = Search.band_filter({ :orderby => 'plays' })
- expect(results.results[0].id).to eq(@band2.id)
- expect(results.results[1].id).to eq(@band1.id)
- end
-
- it "by now playing" do
- # should get 1 result with 1 active session
- session = make_session(@band3)
- #FactoryGirl.create(:active_music_session, :music_session => session)
-
- results = Search.band_filter({ :orderby => 'playing' })
- expect(results.results.count).to be 1
- expect(results.results.first.id).to eq(@band3.id)
-
- # should get 2 results with 2 active sessions
- # sort order should be created_at DESC
- session = make_session(@band4)
- #FactoryGirl.create(:active_music_session, :music_session => session)
- results = Search.band_filter({ :orderby => 'playing' })
- expect(results.results.count).to be 2
- expect(results.results[0].id).to eq(@band4.id)
- expect(results.results[1].id).to eq(@band3.id)
- end
-
- end
-
-
- context 'filter settings' do
- it "searches bands for a genre" do
- genre = FactoryGirl.create(:genre)
- @band1.genres << genre
- @band1.reload
- ggg = @band1.genres.detect { |gg| gg.id == genre.id }
- expect(ggg).to_not be_nil
- results = Search.band_filter({ :genre => ggg.id })
- results.results.each do |rr|
- expect(rr.genres.detect { |gg| gg.id==ggg.id }.id).to eq(genre.id)
- end
- expect(results.results.count).to be 1
- end
-
- it "finds bands within a given distance of given location" do
- pending 'distance search changes'
- num = Band.count
- expect(@band1.lat).to_not be_nil
- # short distance
- results = Search.band_filter({ :per_page => num,
- :distance => 10,
- :city => 'Apex' }, @band1)
- expect(results.results.count).to be num
- # long distance
- results = Search.band_filter({ :per_page => num,
- :distance => 1000,
- :city => 'Miami',
- :state => 'FL' }, @band1)
- expect(results.results.count).to be num
- end
-
- it "finds bands within a given distance of bands location" do
- pending 'distance search changes'
- expect(@band1.lat).to_not be_nil
- # uses the location of @band1
- results = Search.band_filter({ :distance => 10, :per_page => Band.count }, @band1)
- expect(results.results.count).to be Band.count
- end
-
- it "finds no bands within a given distance of location" do
- pending 'distance search changes'
- expect(@band1.lat).to_not be_nil
- results = Search.band_filter({ :distance => 10, :city => 'San Francisco' }, @band1)
- expect(results.results.count).to be 0
end
end
diff --git a/ruby/spec/jam_ruby/models/jam_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_spec.rb
index d17019df7..c1aecf71d 100644
--- a/ruby/spec/jam_ruby/models/jam_track_spec.rb
+++ b/ruby/spec/jam_ruby/models/jam_track_spec.rb
@@ -12,6 +12,90 @@ describe JamTrack do
jam_track = FactoryGirl.create(:jam_track)
jam_track.licensor.should_not be_nil
jam_track.licensor.jam_tracks.should == [jam_track]
+ jam_track.genres.length.should eq(1)
+ end
+
+ describe 'sync_reproduction_royalty' do
+ it "all possible conditions" do
+ jam_track = FactoryGirl.create(:jam_track)
+ jam_track.reproduction_royalty_amount.should be_nil
+
+ jam_track.duration = 0
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.to_f.should eq(0.091)
+
+ jam_track.duration = 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.to_f.should eq(0.091)
+
+ jam_track.duration = 5 * 60 - 1 # just under 5 minutes
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.to_f.should eq(0.091)
+
+ jam_track.duration = 5 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.to_f.should eq(0.091)
+
+ jam_track.duration = 6 * 60 - 1 # just under 6 minutes
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175)
+
+ jam_track.duration = 6 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175)
+
+ jam_track.duration = 7 * 60 - 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 2)
+
+ jam_track.duration = 7 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 2)
+
+ jam_track.duration = 8 * 60 - 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 3)
+
+ jam_track.duration = 8 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 3)
+
+ jam_track.duration = 9 * 60 - 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 4)
+
+ jam_track.duration = 9 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 4)
+
+ jam_track.duration = 10 * 60 - 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 5)
+
+ jam_track.duration = 10 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 5)
+
+ jam_track.duration = 11 * 60 - 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 6)
+
+ jam_track.duration = 11 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 6)
+
+ jam_track.duration = 12 * 60 - 1
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 7)
+
+ jam_track.duration = 12 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 7)
+
+ jam_track.duration = 13 * 60
+ jam_track.save!
+ jam_track.reproduction_royalty_amount.should eq(0.091 + 0.0175 * 8)
+ end
end
describe 'plays' do
@@ -98,6 +182,26 @@ describe JamTrack do
query[1].should eq(jam_track1)
end
+ it "queries on genre" do
+ jam_track1 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'a')
+ jam_track2 = FactoryGirl.create(:jam_track_with_tracks, original_artist: 'artist', name: 'b')
+ jam_track1.genres = [Genre.find('rock')]
+ jam_track2.genres = [Genre.find('asian')]
+ jam_track1.save!
+ jam_track2.save!
+
+ query, pager = JamTrack.index({genre: 'rock'}, user)
+ query.size.should == 1
+ query[0].should eq(jam_track1)
+
+ query, pager = JamTrack.index({genre: 'asian'}, user)
+ query.size.should == 1
+ query[0].should eq(jam_track2)
+
+ query, pager = JamTrack.index({genre: 'african'}, user)
+ query.size.should == 0
+ end
+
it "supports showing purchased only" do
jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'a')
@@ -170,7 +274,7 @@ describe JamTrack do
end
it "100.1234" do
- jam_track = FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100.1234)
+ jam_track = FactoryGirl.build(:jam_track, reproduction_royalty_amount: 100.12345)
jam_track.valid?.should be_false
jam_track.errors[:reproduction_royalty_amount].should == ['is invalid']
end
diff --git a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb
index 6fb4343f6..67a50ec22 100644
--- a/ruby/spec/jam_ruby/models/jam_track_track_spec.rb
+++ b/ruby/spec/jam_ruby/models/jam_track_track_spec.rb
@@ -7,6 +7,7 @@ describe JamTrackTrack do
it "created" do
jam_track_track = FactoryGirl.create(:jam_track_track)
jam_track_track.jam_track.should_not be_nil
+ jam_track_track.jam_track.reload
jam_track_track.jam_track.jam_track_tracks.should == [jam_track_track]
end
diff --git a/web/Gemfile b/web/Gemfile
index 35c68d23c..924910f1b 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -115,7 +115,8 @@ group :development, :test do
gem 'test-unit'
# gem 'teaspoon'
# gem 'teaspoon-jasmine'
- # gem 'puma'
+ gem 'puma'
+ gem 'byebug'
end
group :unix do
gem 'therubyracer' #, '0.11.0beta8'
diff --git a/web/app/assets/javascripts/findBand.js b/web/app/assets/javascripts/findBand.js
deleted file mode 100644
index e7c30f315..000000000
--- a/web/app/assets/javascripts/findBand.js
+++ /dev/null
@@ -1,257 +0,0 @@
-(function(context,$) {
- "use strict";
-
- context.JK = context.JK || {};
- context.JK.FindBandScreen = function(app) {
-
- var logger = context.JK.logger;
- var bands = {};
- var bandList;
- var instrument_logo_map = context.JK.getInstrumentIconMap24();
- var did_show_band_page = false;
- var page_num=1, page_count=0;
- var helpBubble = context.JK.HelpBubbleHelper;
- var $screen = $('#bands-screen');
- var $results = $screen.find('#band-filter-results');
-
- function loadBands(queryString) {
- // squelch nulls and undefines
- queryString = !!queryString ? queryString : "";
-
- $.ajax({
- type: "GET",
- url: "/api/search.json?" + queryString,
- success: afterLoadBands,
- error: app.ajaxError
- });
- }
-
- function search() {
- did_show_band_page = true;
- var queryString = 'srch_b=1&page='+page_num+'&';
-
- // order by
- var orderby = $('#band_order_by').val();
- if (typeof orderby != 'undefined' && orderby.length > 0) {
- queryString += "orderby=" + orderby + '&';
- }
- // genre filter
- var genre = $('#band_genre').val();
- if (typeof genre != 'undefined' && !(genre === '')) {
- queryString += "genre=" + genre + '&';
- }
- // distance filter
- var query_param = $('#band_query_distance').val();
- if (query_param !== null && query_param.length > 0) {
- var matches = query_param.match(/(\d+)/);
- if (0 < matches.length) {
- var distance = matches[0];
- queryString += "distance=" + distance + '&';
- }
- }
- loadBands(queryString);
- }
-
- function refreshDisplay() {
- clearResults();
- search();
- }
-
- function afterLoadBands(mList) {
- // display the 'no bands' banner if appropriate
- var $noBandsFound = $('#bands-none-found');
- bandList = mList;
-
- if(bandList.length == 0) {
- $noBandsFound.show();
- bands = [];
- }
- else {
- $noBandsFound.hide();
- bands = bandList['bands'];
- if (!(typeof bands === 'undefined')) {
- $('#band-filter-city').text(bandList['city']);
- if (0 == page_count) {
- page_count = bandList['page_count'];
- }
- renderBands();
- }
- }
- }
-
- function renderBands() {
- var ii, len;
- var mTemplate = $('#template-find-band-row').html();
- var pTemplate = $('#template-band-player-info').html();
- var aTemplate = $('#template-band-action-btns').html();
- var eTemplate = $('#template-band-edit-btns').html();
- var bVals, bb, renderings='';
- var instr_logos, instr;
- var players, playerVals, aPlayer, isMember;
-
- for (ii=0, len=bands.length; ii < len; ii++) {
- bb = bands[ii];
- instr_logos = '';
- players = '';
- playerVals = {};
- isMember = false;
- for (var jj=0, ilen=bb['players'].length; jj';
- }
- if (!isMember) {
- isMember = aPlayer.user_id == context.JK.currentUserId;
- }
- playerVals = {
- user_id: aPlayer.user_id,
- player_name: aPlayer.name,
- profile_url: '/client#/profile/' + aPlayer.user_id,
- avatar_url: context.JK.resolveAvatarUrl(aPlayer.photo_url),
- player_instruments: player_instrs
- };
-
- players += context.JK.fillTemplate(pTemplate, playerVals);
- }
-
- var actionVals, band_actions;
- if (isMember) {
- actionVals = {
- profile_url: "/client#/bandProfile/" + bb.id,
- band_edit_url: "/client#/band/setup/" + bb.id + '/step1',
- band_member_url: "/client#/band/setup/" + bb.id + '/step2'
- };
- band_actions = context.JK.fillTemplate(eTemplate, actionVals);
- } else {
- actionVals = {
- profile_url: "/client#/bandProfile/" + bb.id,
- button_follow: bb['is_following'] ? '' : 'button-orange',
- button_message: 'button-orange'
- };
- band_actions = context.JK.fillTemplate(aTemplate, actionVals);
- }
-
- var bgenres = '';
- for (jj=0, ilen=bb['genres'].length; jj';
- }
-
- bgenres += '
';
-
- bVals = {
- avatar_url: context.JK.resolveBandAvatarUrl(bb.photo_url),
- profile_url: "/client#/bandProfile/" + bb.id,
- band_name: bb.name,
- band_location: bb.city + ', ' + bb.state,
- genres: bgenres,
- instruments: instr_logos,
- biography: bb['biography'],
- follow_count: bb['follow_count'],
- recording_count: bb['recording_count'],
- session_count: bb['session_count'],
- band_id: bb['id'],
- band_player_template: players,
- band_action_template: band_actions
- };
-
- var $rendering = $(context.JK.fillTemplate(mTemplate, bVals))
-
- var $offsetParent = $results.closest('.content');
- var data = {entity_type: 'band'};
-
- var options = {positions: ['top', 'bottom', 'right', 'left'], offsetParent: $offsetParent};
- context.JK.helpBubble($('.follower-count', $rendering), 'follower-count', data, options);
- context.JK.helpBubble($('.recording-count', $rendering), 'recording-count', data, options);
- context.JK.helpBubble($('.session-count', $rendering), 'session-count', data, options);
-
- $results.append($rendering);
- }
-
- $('.search-m-follow').on('click', followBand);
- context.JK.bindHoverEvents();
- }
-
- function beforeShow(data) {
- }
-
- function afterShow(data) {
- if (!did_show_band_page) {
- refreshDisplay();
- }
- }
-
- function clearResults() {
- bands = {};
- $('#band-filter-results').empty();
- page_num = 1;
- page_count = 0;
- }
-
- function followBand(evt) {
- // if the band is already followed, remove the button-orange class, and prevent
- // the link from working
- if (0 == $(this).closest('.button-orange').size()) return false;
- $(this).click(function(ee) {ee.preventDefault();});
-
- evt.stopPropagation();
- var newFollowing = {};
- newFollowing.band_id = $(this).parent().data('band-id');
- var url = "/api/users/" + context.JK.currentUserId + "/followings";
- $.ajax({
- type: "POST",
- dataType: "json",
- contentType: 'application/json',
- url: url,
- data: JSON.stringify(newFollowing),
- processData: false,
- success: function(response) {
- // remove the orange look to indicate it's not selectable
- // @FIXME -- this will need to be tweaked when we allow unfollowing
- $('div[data-band-id='+newFollowing.band_id+'] .search-m-follow').removeClass('button-orange').addClass('button-grey');
- },
- error: app.ajaxError(arguments)
- });
- }
-
- function events() {
- $('#band_query_distance').change(refreshDisplay);
- $('#band_genre').change(refreshDisplay);
- $('#band_order_by').change(refreshDisplay);
-
- $('#band-filter-results').closest('.content-body-scroller').bind('scroll', function() {
- if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
- if (page_num < page_count) {
- page_num += 1;
- search();
- }
- }
- });
- }
-
- function initialize() {
- var screenBindings = {
- 'beforeShow': beforeShow,
- 'afterShow': afterShow
- };
- app.bindScreen('bands', screenBindings);
-
- events();
- }
-
- this.initialize = initialize;
- this.renderBands = renderBands;
- this.afterShow = afterShow;
- this.clearResults = clearResults;
-
- return this;
- }
-})(window,jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 22ca5db52..87f964575 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -1798,6 +1798,19 @@
});
}
+ function getBandSearchFilter(query) {
+ var qarg = query === undefined ? '' : query;
+ return $.get("/api/search/bands.json?"+qarg);
+ }
+
+ function postBandSearchFilter(query) {
+ return $.ajax({
+ type: "POST",
+ url: "/api/search/bands.json",
+ data: query
+ });
+ }
+
function getMount(options) {
var id = getId(options);
return $.ajax({
@@ -2046,6 +2059,8 @@
this.addRecordingTimeline = addRecordingTimeline;
this.getMusicianSearchFilter = getMusicianSearchFilter;
this.postMusicianSearchFilter = postMusicianSearchFilter;
+ this.getBandSearchFilter = getBandSearchFilter;
+ this.postBandSearchFilter = postBandSearchFilter;
this.playJamTrack = playJamTrack;
this.createSignupHint = createSignupHint;
this.createAlert = createAlert;
diff --git a/web/app/assets/javascripts/member_search_filter.js.coffee b/web/app/assets/javascripts/member_search_filter.js.coffee
new file mode 100644
index 000000000..357806ebc
--- /dev/null
+++ b/web/app/assets/javascripts/member_search_filter.js.coffee
@@ -0,0 +1,858 @@
+$ = jQuery
+context = window
+context.JK ||= {};
+
+context.JK.BaseSearchFilter = class BaseSearchFilter
+
+ constructor: () ->
+ @rest = context.JK.Rest()
+ @logger = context.JK.logger
+ @searchFilter = null
+ @profileUtils = context.JK.ProfileUtils
+ @helpBubble = context.JK.HelpBubbleHelper
+ @searchResults = null
+ @isSearching = false
+ @pageNumber = 1
+ @instrument_logo_map = context.JK.getInstrumentIconMap24()
+ @searchType = ''
+ @searchTypeS = ''
+ @restGet = null
+ @restPost = null
+ @searchMeta = null
+
+ init: (app) =>
+ @app = app
+ @screenBindings = { 'afterShow': this.afterShow, 'afterHide': this.afterHide }
+ @app.bindScreen(@searchTypeS, @screenBindings)
+
+ @screen = $('#'+@searchTypeS+'-screen')
+ @resultsListContainer = @screen.find('#'+@searchType+'-search-filter-results-list')
+ @spinner = @screen.find('.paginate-wait')
+
+ this.registerResultsPagination()
+
+ afterShow: () =>
+ @screen.find('#'+@searchType+'-search-filter-results').show()
+ @screen.find('#'+@searchType+'-search-filter-builder').hide()
+ this.getUserFilterResults()
+
+ showBuilder: () =>
+ @screen.find('#'+@searchType+'-search-filter-results').hide()
+ @screen.find('#'+@searchType+'-search-filter-builder').show()
+ @resultsListContainer.empty()
+
+ afterHide: () =>
+ @resultsListContainer.empty()
+
+ searchMetaData: () =>
+ @searchMeta
+
+ filterData: () =>
+ @searchFilter.data_blob
+
+ renderSearchFilter: () =>
+ $.when(@restGet()).done (sFilter) =>
+ this.loadSearchFilter(sFilter)
+
+ loadSearchFilter: (sFilter) =>
+
+ _populateSelectWithKeys: (struct, selection, keys, element) =>
+ element.children().remove()
+ $.each keys, (idx, value) =>
+ label = struct[value]
+ blankOption = $ ''
+ blankOption.text label
+ blankOption.attr 'value', value
+ blankOption.attr 'selected', '' if value == selection
+ element.append(blankOption)
+ context.JK.dropdown(element)
+
+ _populateSelectIdentifier: (identifier) =>
+ elem = $ '#'+@searchType+'-search-filter-builder select[name='+identifier+']'
+ struct = this.searchMetaData()[identifier]['map']
+ keys = this.searchMetaData()[identifier]['keys']
+ this._populateSelectWithKeys(struct, this.filterData()[identifier], keys, elem)
+
+ _populateSelectWithInt: (sourceStruct, selection, element) =>
+ struct =
+ '-1': 'Any'
+ $.extend(struct, sourceStruct)
+ this._populateSelectWithKeys(struct, selection, Object.keys(struct).sort(), element)
+
+ _populateSortOrder: () =>
+ this._populateSelectIdentifier('sort_order')
+
+ _populateGigs: () =>
+ elem = $ '#'+@searchType+'-search-filter-builder select[name=concert_gigs]'
+ this._populateSelectWithInt(@profileUtils.gigMap, this.filterData().concert_gigs.toString(), elem)
+
+ _populateGenres: () =>
+ @screen.find('#search-filter-genres').empty()
+ @rest.getGenres().done (genres) =>
+ genreTemplate = @screen.find('#template-search-filter-setup-genres').html()
+ filterGenres = this.filterData().genres
+ $.each genres, (index, genre) =>
+ if 0 < filterGenres.length
+ genreMatch = $.grep(filterGenres, (n, i) ->
+ n == genre.id)
+ else
+ genreMatch = []
+ if genreMatch.length > 0 then selected = 'checked' else selected = ''
+ genreHtml = context.JK.fillTemplate(genreTemplate,
+ id: genre.id
+ description: genre.description
+ checked: selected)
+ @screen.find('#search-filter-genres').append genreHtml
+
+ _populateInstruments: () =>
+ @screen.find('#search-filter-instruments').empty()
+ @rest.getInstruments().done (instruments) =>
+ $.each instruments, (index, instrument) =>
+ instrumentTemplate = @screen.find('#template-search-filter-setup-instrument').html()
+ selected = ''
+ proficiency = '1'
+ if 0 < this.filterData().instruments.length
+ instMatch = $.grep(this.filterData().instruments, (inst, i) ->
+ yn = inst.instrument_id == instrument.id
+ proficiency = inst.proficiency_level if yn
+ yn)
+ selected = 'checked' if instMatch.length > 0
+ instrumentHtml = context.JK.fillTemplate(instrumentTemplate,
+ id: instrument.id
+ description: instrument.description
+ checked: selected)
+ @screen.find('#search-filter-instruments').append instrumentHtml
+ profsel = '#search-filter-instruments tr[data-instrument-id="'+instrument.id+'"] select'
+ jprofsel = @screen.find(profsel)
+ jprofsel.val(proficiency)
+ context.JK.dropdown(jprofsel)
+ return true
+
+ _builderSelectValue: (identifier) =>
+ elem = $ '#'+@searchType+'-search-filter-builder select[name='+identifier+']'
+ elem.val()
+
+ _builderSelectMultiValue: (identifier) =>
+ vals = []
+ elem = $ '#search-filter-'+identifier+' input[type=checkbox]:checked'
+ if 'instruments' == identifier
+ elem.each (idx) ->
+ row = $(this).parent().parent()
+ instrument =
+ instrument_id: row.data('instrument-id')
+ proficiency_level: row.find('select').val()
+ vals.push instrument
+ else
+ elem.each (idx) ->
+ if $(this).prop('checked')
+ vals.push $(this).val()
+ vals
+
+ willSearch: (reload) =>
+ return false if @isSearching
+ @isSearching = true
+ if reload
+ @pageNumber = 1
+ @screen.find('#'+@searchType+'-search-filter-spinner').show()
+ @resultsListContainer.empty()
+ @screen.find('#'+@searchType+'-search-filter-builder').hide()
+ @screen.find('#'+@searchType+'-search-filter-results').show()
+ true
+
+ didSearch: (response) =>
+ this.loadSearchFilter(response.filter_json)
+ @searchResults = response
+ @screen.find('#'+@searchType+'-search-filter-spinner').hide()
+ this.renderResultsPage()
+ @screen.find('.paginate-wait').hide()
+ @isSearching = false
+
+ resetFilter: () =>
+ if this.willSearch(true)
+ @restPost({ filter: 'reset' }).done(this.didSearch)
+
+ cancelFilter: () =>
+ this.resetFilter()
+
+ getUserFilterResults: () =>
+ if this.willSearch(true)
+ this.doRestGet()
+
+ doRestGet: (query) =>
+ query2 = 'results=true&'
+ unless (typeof query == "undefined")
+ query2 += query
+ @restGet(query2).done(this.didSearch)
+
+ performSearch: () =>
+ if this.willSearch(true)
+ $.each this.searchMetaData().filter_keys.single, (index, key) =>
+ this.filterData()[key] = this._builderSelectValue(key)
+ $.each this.searchMetaData().filter_keys.multi, (index, key) =>
+ this.filterData()[key] = this._builderSelectMultiValue(key)
+ @restPost({ filter: JSON.stringify(this.filterData()), page: @pageNumber }).done(this.didSearch)
+
+ renderResultsHeader: () =>
+
+ renderResultsPage: () =>
+
+ _formatLocation: (band) ->
+ if band.city and band.state
+ band.city + ', ' + band.state
+ else if band.city
+ band.city
+ else if band.regionname
+ band.regionname
+ else
+ 'Location Unavailable'
+
+ friendRequestCallback: (user_id)=>
+ # TODO:
+
+ paginate: () =>
+ if @pageNumber < @searchResults.page_count && this.willSearch(false)
+ @screen.find('.paginate-wait').show()
+ @pageNumber += 1
+ @restPost({ filter: JSON.stringify(this.filterData()), page: @pageNumber }).done(this.didSearch)
+ return true
+ false
+
+ registerResultsPagination: () =>
+ _resultsListContainer = @resultsListContainer
+ _headerHeight = @screen.find('#'+@searchType+'-search-filter-results-header').height()
+ _paginator = this.paginate
+
+ @screen.find('.content-body-scroller').scroll ->
+ if _resultsListContainer.is(':visible')
+ jthis = $(this)
+ wintop = jthis.scrollTop()
+ winheight = jthis.innerHeight()
+ docheight = jthis[0].scrollHeight - _headerHeight
+ scrollTrigger = 0.98;
+ if ((wintop / (docheight - winheight)) >= scrollTrigger)
+ _paginator()
+
+
+context.JK.MusicianSearchFilter = class MusicianSearchFilter extends BaseSearchFilter
+
+ constructor: () ->
+ super()
+ @searchType = 'musician'
+ @searchTypeS = @searchType+'s'
+ @restGet = @rest.getMusicianSearchFilter
+ @restPost = @rest.postMusicianSearchFilter
+ @searchMeta = gon.musician_search_meta
+
+ init: (app) =>
+ super(app)
+ @screen.find('#btn-'+@searchType+'-search-builder').on 'click', =>
+ this.showBuilder()
+ @screen.find('#btn-'+@searchType+'-search-reset').on 'click', =>
+ this.resetFilter()
+
+ renderSearchFilter: () =>
+ super()
+
+ loadSearchFilter: (sFilter) =>
+ super(sFilter)
+
+ @searchFilter = JSON.parse(sFilter)
+ args =
+ interests: this.filterData().interests
+ skill_level: this.filterData().skill_level
+ studio_sessions: this.filterData().studio_sessions
+ concert_gigs: this.filterData().concert_gigs
+
+ template = context.JK.fillTemplate(@screen.find('#template-musician-search-filter').html(), args)
+
+ content_root = @screen.find('#musician-search-filter-builder')
+ content_root.html template
+
+ @screen.find('#btn-perform-musician-search').on 'click', =>
+ this.performSearch()
+
+ @screen.find('#btn-musician-search-cancel').on 'click', =>
+ this.cancelFilter()
+
+ this._populateSkill()
+ this._populateStudio()
+ this._populateGigs()
+ this._populateInterests()
+ this._populateAges()
+ this._populateGenres()
+ this._populateInstruments()
+ this._populateSortOrder()
+
+ _populateSortOrder: () =>
+ this._populateSelectIdentifier('sort_order')
+
+ _populateInterests: () =>
+ this._populateSelectIdentifier('interests')
+
+ _populateStudio: () =>
+ elem = $ '#musician-search-filter-builder select[name=studio_sessions]'
+ this._populateSelectWithInt(@profileUtils.studioMap, this.filterData().studio_sessions.toString(), elem)
+
+ _populateAges: () =>
+ @screen.find('#search-filter-ages').empty()
+ ages_map = this.searchMetaData()['ages']['map']
+ $.each this.searchMetaData()['ages']['keys'], (index, key) =>
+ ageTemplate = @screen.find('#template-search-filter-setup-ages').html()
+ selected = ''
+ ageLabel = ages_map[key]
+ if 0 < this.filterData().ages.length
+ key_val = key.toString()
+ ageMatch = $.grep(this.filterData().ages, (n, i) ->
+ n == key_val)
+ selected = 'checked' if ageMatch.length > 0
+ ageHtml = context.JK.fillTemplate(ageTemplate,
+ id: key
+ description: ageLabel
+ checked: selected)
+ @screen.find('#search-filter-ages').append ageHtml
+
+ _populateGenres: () =>
+ super()
+
+ _populateSkill: () =>
+ elem = $ '#'+@searchType+'-search-filter-builder select[name=skill_level]'
+ this._populateSelectWithInt(@profileUtils.skillLevelMap, this.filterData().skill_level.toString(), elem)
+
+ _populateInstruments: () =>
+ super()
+
+ willSearch: (reload) =>
+ super(reload)
+
+ didSearch: (response) =>
+ super(response)
+
+ resetFilter: () =>
+ super()
+
+ cancelFilter: () =>
+ super()
+
+ getUserFilterResults: () =>
+ super()
+
+ performSearch: () =>
+ super()
+
+ renderResultsHeader: () =>
+ @screen.find('#'+@searchType+'-search-filter-description').html(@searchResults.description)
+ if @searchResults.is_blank_filter
+ @screen.find('#btn-'+@searchType+'-search-reset').hide()
+ else
+ @screen.find('#btn-'+@searchType+'-search-reset').show()
+
+ renderResultsPage: () =>
+ super()
+ this.renderResultsHeader() if @pageNumber == 1
+ musicians = @searchResults.musicians
+ len = musicians.length
+ if 0 == len
+ @screen.find('#musician-search-filter-results-list-blank').show()
+ @screen.find('#musician-search-filter-results-list-blank').html('No results found')
+ return
+ else
+ @screen.find('#musician-search-filter-results-list-blank').hide()
+
+ ii = 0
+ mTemplate = @screen.find('#template-search-musician-row').html()
+ aTemplate = @screen.find('#template-search-musician-action-btns').html()
+ mVals = undefined
+ musician = undefined
+ renderings = ''
+ instr_logos = undefined
+ follows = undefined
+ followVals = undefined
+ aFollow = undefined
+ myAudioLatency = @searchResults.my_audio_latency
+ while ii < len
+ musician = musicians[ii]
+ if context.JK.currentUserId == musician.id
+ ii++
+ continue
+ instr_logos = ''
+ jj = 0
+ ilen = musician['instruments'].length
+ while jj < ilen
+ instr_id = musician['instruments'][jj].instrument_id
+ if instr_img = @instrument_logo_map[instr_id]
+ instr_logos += '
'
+ jj++
+ actionVals =
+ profile_url: '/client#/profile/' + musician.id
+ friend_class: 'button-' + (if musician['is_friend'] then 'grey' else 'orange')
+ friend_caption: (if musician.is_friend then 'DIS' else '') + 'CONNECT'
+ follow_class: 'button-' + (if musician['is_following'] then 'grey' else 'orange')
+ follow_caption: (if musician.is_following then 'UN' else '') + 'FOLLOW'
+ message_class: 'button-orange'
+ message_caption: 'MESSAGE'
+ button_message: 'button-orange'
+ musician_actions = context.JK.fillTemplate(aTemplate, actionVals)
+ latencyBadge = context._.template($("#template-account-session-latency").html(), $.extend(sessionUtils.createLatency(musician), musician), variable: 'data')
+ mVals =
+ avatar_url: context.JK.resolveAvatarUrl(musician.photo_url)
+ profile_url: '/client#/profile/' + musician.id
+ musician_name: musician.name
+ musician_location: this._formatLocation(musician)
+ instruments: instr_logos
+ biography: musician['biography']
+ follow_count: musician['follow_count']
+ friend_count: musician['friend_count']
+ recording_count: musician['recording_count']
+ session_count: musician['session_count']
+ musician_id: musician['id']
+ musician_action_template: musician_actions
+ latency_badge: latencyBadge
+ musician_first_name: musician['first_name']
+ $rendering = $(context.JK.fillTemplate(mTemplate, mVals))
+ $offsetParent = @resultsListContainer.closest('.content')
+ data = entity_type: 'musician'
+ options =
+ positions: [
+ 'top'
+ 'bottom'
+ 'right'
+ 'left'
+ ]
+ offsetParent: $offsetParent
+ scoreOptions = offsetParent: $offsetParent
+ context.JK.helpBubble($('.follower-count', $rendering), 'follower-count', data, options);
+ context.JK.helpBubble($('.friend-count', $rendering), 'friend-count', data, options);
+ context.JK.helpBubble($('.recording-count', $rendering), 'recording-count', data, options);
+ context.JK.helpBubble($('.session-count', $rendering), 'session-count', data, options);
+ @helpBubble.scoreBreakdown $('.latency', $rendering), false, musician['full_score'], myAudioLatency, musician['audio_latency'], musician['score'], scoreOptions
+ @resultsListContainer.append $rendering
+ $rendering.find('.biography').dotdotdot()
+ ii++
+
+ this._bindMessageMusician()
+ this._bindFriendMusician()
+ this._bindFollowMusician()
+
+ context.JK.bindHoverEvents()
+ return
+
+ _bindMessageMusician: () =>
+ objThis = this
+ @screen.find('.search-m-message').on 'click', (evt) ->
+ userId = $(this).parent().data('musician-id')
+ objThis.app.layout.showDialog 'text-message', d1: userId
+
+
+ _bindFriendMusician: () =>
+ objThis = this
+ @screen.find('.search-m-friend').on 'click', (evt) ->
+ # if the musician is already a friend, remove the button-orange class, and prevent the link from working
+ if 0 == $(this).closest('.button-orange').size()
+ return false
+ $(this).click (ee) ->
+ ee.preventDefault()
+ return
+ evt.stopPropagation()
+ uid = $(this).parent().data('musician-id')
+ objThis.rest.sendFriendRequest objThis.app, uid, this.friendRequestCallback
+
+ _bindFollowMusician: () =>
+ objThis = this
+ @screen.find('.search-m-follow').on 'click', (evt) ->
+ # if the musician is already followed, remove the button-orange class, and prevent the link from working
+ if 0 == $(this).closest('.button-orange').size()
+ return false
+ $(this).click (ee) ->
+ ee.preventDefault()
+ return
+ evt.stopPropagation()
+ newFollowing = {}
+ newFollowing.user_id = $(this).parent().data('musician-id')
+ url = '/api/users/' + context.JK.currentUserId + '/followings'
+ $.ajax
+ type: 'POST'
+ dataType: 'json'
+ contentType: 'application/json'
+ url: url
+ data: JSON.stringify(newFollowing)
+ processData: false
+ success: (response) ->
+ # remove the orange look to indicate it's not selectable
+ # @FIXME -- this will need to be tweaked when we allow unfollowing
+ objThis.screen.find('div[data-musician-id=' + newFollowing.user_id + '] .search-m-follow').removeClass('button-orange').addClass 'button-grey'
+ return
+ error: objThis.app.ajaxError
+
+ _formatLocation: (musician) ->
+ if musician.city and musician.state
+ musician.city + ', ' + musician.state
+ else if musician.city
+ musician.city
+ else if musician.regionname
+ musician.regionname
+ else
+ 'Location Unavailable'
+
+ friendRequestCallback: (user_id)=>
+ # TODO:
+
+ paginate: () =>
+ super()
+
+ registerResultsPagination: () =>
+ super()
+
+
+context.JK.BandSearchFilter = class BandSearchFilter extends BaseSearchFilter
+
+ constructor: () ->
+ super()
+ @searchType = 'band'
+ @searchTypeS = @searchType+'s'
+ @restGet = @rest.getBandSearchFilter
+ @restPost = @rest.postBandSearchFilter
+ @searchMeta = gon.band_search_meta
+ @searchSubType = 'to_join'
+
+ init: (app) =>
+ super(app)
+
+ @screen.find('#btn-'+@searchType+'-search-builder-to_join').on 'click', =>
+ this.showBuilderToJoin()
+ @screen.find('#btn-'+@searchType+'-search-builder-to_hire').on 'click', =>
+ this.showBuilderToHire()
+ @screen.find('#btn-'+@searchType+'-search-builder').on 'click', =>
+ this.showBuilderActive()
+ @screen.find('#btn-'+@searchType+'-search-reset').on 'click', =>
+ this.resetFilter()
+
+ @resultsList = @screen.find('#band-search-filter-results-list')
+ @screen.find('#band-search-filter-results-filtered').hide()
+
+ isToHire: () =>
+ 'to_hire' == @searchSubType
+
+ showBuilderActive: () =>
+ if this.isToHire()
+ this.showBuilderToHire()
+ else
+ this.showBuilderToJoin()
+
+ showBuilderToJoin: () =>
+ @screen.find('.band-search-filter-builder-top-to_join').show()
+ @screen.find('.band-search-filter-builder-top-to_hire').hide()
+ @searchSubType = 'to_join'
+ this.showBuilder()
+ this._loadSearchFilter() if @searchFilter
+ # @screen.find('.band-search-filter-builder-top-to_join h2').html('search bands')
+
+ showBuilderToHire: () =>
+ @screen.find('.band-search-filter-builder-top-to_join').hide()
+ @screen.find('.band-search-filter-builder-top-to_hire').show()
+ @searchSubType = 'to_hire'
+ this.showBuilder()
+ this._loadSearchFilter() if @searchFilter
+ # @screen.find('.band-search-filter-builder-top-to_hire h2').html('search bands to hire')
+
+ searchMetaData: () =>
+ @searchMeta[@searchSubType]
+
+ renderSearchFilter: () =>
+ super()
+
+ _searchFilterArgsToJoin: () =>
+ args =
+ touring_option: this.filterData().touring_option
+ band_status: this.filterData().band_status
+ play_commitment: this.filterData().play_commitment
+ band_type: this.filterData().band_type
+ concert_gigs: this.filterData().concert_gigs
+
+ _searchFilterArgsToHire: () =>
+ if 0 < this.filterData().max_cost
+ has_max_cost = 'checked'
+ else
+ has_max_cost = ''
+ if 1==this.filterData().free_gigs
+ has_free_gigs = 'checked'
+ else
+ has_free_gigs = ''
+
+ args =
+ band_status: this.filterData().band_status
+ concert_gigs: this.filterData().concert_gigs
+ performance_samples: this.filterData().performance_samples
+ has_max_cost: has_max_cost
+ max_cost: this.filterData().max_cost
+ has_free_gigs: has_free_gigs
+
+ _populateSearchFilterToJoin: () =>
+ this._populateInstruments()
+ this._populateSkill()
+ this._populateGigs()
+ this._populatePlayCommit()
+ this._populateTourOption()
+ this._populateBandStatus()
+
+ _populateSearchFilterToHire: () =>
+ this._populateBandStatus()
+ this._populateGigs()
+ this._populatePerformSamples()
+
+ loadSearchFilter: (sFilter) =>
+ super(sFilter)
+ @searchFilter = JSON.parse(sFilter)
+ this._loadSearchFilter()
+
+ _loadSearchFilter: () =>
+ switch @searchSubType
+ when 'to_join' then args = this._searchFilterArgsToJoin()
+ when 'to_hire' then args = this._searchFilterArgsToHire()
+
+ template = context.JK.fillTemplate(@screen.find('#template-band-search-filter-'+@searchSubType).html(), args)
+
+ content_root = @screen.find('#band-search-filter-builder')
+ content_root.html template
+
+ @screen.find('#btn-perform-band-search').on 'click', =>
+ this.performSearch()
+
+ @screen.find('#btn-band-search-cancel').on 'click', =>
+ this.cancelFilter()
+
+ this._populateGenres()
+ this._populateSortOrder() if this.isToHire()
+
+ switch @searchSubType
+ when 'to_join' then this._populateSearchFilterToJoin()
+ when 'to_hire' then this._populateSearchFilterToHire()
+
+ _populateSkill: () =>
+ this._populateSelectIdentifier('band_type')
+
+ _populateBandStatus: () =>
+ this._populateSelectIdentifier('band_status')
+
+ _populatePlayCommit: () =>
+ this._populateSelectIdentifier('play_commitment')
+
+ _populateTourOption: () =>
+ this._populateSelectIdentifier('touring_option')
+
+ _populateSortOrder: () =>
+ this._populateSelectIdentifier('sort_order')
+
+ _populatePerformSamples: () =>
+ this._populateSelectIdentifier('performance_samples')
+
+ _populateGenres: () =>
+ super()
+
+ _populateInstruments: () =>
+ super()
+
+ willSearch: (reload) =>
+ super(reload)
+
+ didSearch: (response) =>
+ super(response)
+
+ resetFilter: () =>
+ if this.willSearch(true)
+ @screen.find('#band-search-filter-results-blank').show()
+ @screen.find('#band-search-filter-results-filtered').hide()
+ @screen.find('#band-search-filter-description').html('')
+ @restPost({ filter: 'reset', subtype: @searchSubType }).done(this.didSearch)
+
+ cancelFilter: () =>
+ super()
+
+ doRestGet: (query) =>
+ super('subtype='+@searchSubType)
+
+ performSearch: () =>
+ if this.willSearch(true)
+ filterPost = this.filterData()
+ $.each this.searchMetaData().filter_keys.single, (index, key) =>
+ filterPost[key] = this._builderSelectValue(key)
+ $.each this.searchMetaData().filter_keys.multi, (index, key) =>
+ filterPost[key] = this._builderSelectMultiValue(key)
+
+ if this.isToHire()
+ filterPost['max_cost'] = parseInt($('#max_cost_amount').val())
+ filterPost['free_gigs'] = if $('#free_gigs').prop('checked') then 1 else 0
+
+ postData = { subtype: @searchSubType, filter: JSON.stringify(filterPost), page: @pageNumber }
+ @restPost(postData).done(this.didSearch)
+
+ renderResultsHeader: () =>
+ if @searchResults.is_blank_filter
+ @screen.find('#band-search-filter-results-blank').show()
+ @screen.find('#band-search-filter-results-filtered').hide()
+ else
+ @screen.find('#band-search-filter-results-blank').hide()
+ @screen.find('#band-search-filter-results-filtered').show()
+ @screen.find('#band-search-filter-description').html(@searchResults.description)
+
+ renderResultsPage: () =>
+ super()
+ this.renderResultsHeader() if @pageNumber == 1
+ bands = @searchResults.bands
+ len = bands.length
+ if 0 == len
+ @screen.find('#band-search-filter-results-list').hide()
+ @screen.find('#band-search-filter-results-list-blank').show()
+ @screen.find('#band-search-filter-results-list-blank').html('No results found')
+ return
+ else
+ @screen.find('#band-search-filter-results-list-blank').hide()
+ @screen.find('#band-search-filter-results-list').show()
+ this.renderBands(bands)
+
+ renderBands: (bands) =>
+ toolTip = undefined
+ ii = undefined
+ len = undefined
+ mTemplate = $('#template-find-band-row').html()
+ pTemplate = $('#template-band-player-info').html()
+ aTemplate = $('#template-band-action-btns').html()
+ eTemplate = $('#template-band-edit-btns').html()
+ bVals = undefined
+ bb = undefined
+ renderings = ''
+ instr_logos = undefined
+ instr = undefined
+ players = undefined
+ playerVals = undefined
+ aPlayer = undefined
+ isMember = undefined
+ ii = 0
+ len = bands.length
+ while ii < len
+ bb = bands[ii]
+ instr_logos = ''
+ players = ''
+ playerVals = {}
+ isMember = false
+ jj = 0
+ ilen = bb['players'].length
+ while jj < ilen
+ toolTip = ''
+ aPlayer = bb['players'][jj]
+ player_instrs = ''
+ iter_pinstruments = aPlayer['instruments'].split(',')
+ kk = 0
+ klen = iter_pinstruments.length
+ while kk < klen
+ pinstr = iter_pinstruments[kk]
+ toolTip = ''
+ if pinstr of @instrument_logo_map
+ instr = @instrument_logo_map[pinstr].asset
+ toolTip = pinstr
+ player_instrs += '
'
+ kk++
+ if !isMember
+ isMember = aPlayer.user_id == context.JK.currentUserId
+ playerVals =
+ user_id: aPlayer.user_id
+ player_name: aPlayer.name
+ profile_url: '/client#/profile/' + aPlayer.user_id
+ avatar_url: context.JK.resolveAvatarUrl(aPlayer.photo_url)
+ player_instruments: player_instrs
+ players += context.JK.fillTemplate(pTemplate, playerVals)
+ jj++
+ actionVals = undefined
+ band_actions = undefined
+ if isMember
+ actionVals =
+ profile_url: '/client#/bandProfile/' + bb.id
+ band_edit_url: '/client#/band/setup/' + bb.id + '/step1'
+ band_member_url: '/client#/band/setup/' + bb.id + '/step2'
+ band_actions = context.JK.fillTemplate(eTemplate, actionVals)
+ else
+ actionVals =
+ profile_url: '/client#/bandProfile/' + bb.id
+ button_follow: if bb['is_following'] then '' else 'button-orange'
+ button_message: 'button-orange'
+ band_actions = context.JK.fillTemplate(aTemplate, actionVals)
+ bgenres = ''
+ jj = 0
+ ilen = bb['genres'].length
+ while jj < ilen
+ bgenres += bb['genres'][jj]['description'] + '
'
+ jj++
+ bgenres += '
'
+ bVals =
+ avatar_url: context.JK.resolveBandAvatarUrl(bb.photo_url)
+ profile_url: '/client#/bandProfile/' + bb.id
+ band_name: bb.name
+ band_location: bb.city + ', ' + bb.state
+ genres: bgenres
+ instruments: instr_logos
+ biography: bb['biography']
+ follow_count: bb['follow_count']
+ recording_count: bb['recording_count']
+ session_count: bb['session_count']
+ band_id: bb['id']
+ band_player_template: players
+ band_action_template: band_actions
+ $rendering = $(context.JK.fillTemplate(mTemplate, bVals))
+ $offsetParent = @resultsList.closest('.content')
+ data = entity_type: 'band'
+ options =
+ positions: [
+ 'top'
+ 'bottom'
+ 'right'
+ 'left'
+ ]
+ offsetParent: $offsetParent
+ context.JK.helpBubble $('.follower-count', $rendering), 'follower-count', data, options
+ context.JK.helpBubble $('.recording-count', $rendering), 'recording-count', data, options
+ context.JK.helpBubble $('.session-count', $rendering), 'session-count', data, options
+ @resultsList.append $rendering
+ ii++
+
+ this._bindFollowBand()
+
+ context.JK.bindHoverEvents()
+ return
+
+ _bindFollowBand: () =>
+ objThis = this
+ @screen.find('.search-m-follow').on 'click', (evt) ->
+ if 0 == $(this).closest('.button-orange').size()
+ return false
+ $(this).click (ee) ->
+ ee.preventDefault()
+ return
+ evt.stopPropagation()
+ newFollowing = {}
+ newFollowing.band_id = $(this).parent().data('band-id')
+ url = '/api/users/' + context.JK.currentUserId + '/followings'
+ $.ajax
+ type: 'POST'
+ dataType: 'json'
+ contentType: 'application/json'
+ url: url
+ data: JSON.stringify(newFollowing)
+ processData: false
+ success: (response) ->
+ $('div[data-band-id=' + newFollowing.band_id + '] .search-m-follow').removeClass('button-orange').addClass 'button-grey'
+ return
+ error: objThis.app.ajaxError(arguments)
+
+ _formatLocation: (band) ->
+
+ friendRequestCallback: (user_id)=>
+ # TODO:
+
+ paginate: () =>
+ super()
+
+ registerResultsPagination: () =>
+ super()
+
+ filterData: () =>
+ super()[@searchSubType]
+
diff --git a/web/app/assets/javascripts/musician_search_filter.js.coffee b/web/app/assets/javascripts/musician_search_filter.js.coffee
deleted file mode 100644
index 9586374b8..000000000
--- a/web/app/assets/javascripts/musician_search_filter.js.coffee
+++ /dev/null
@@ -1,412 +0,0 @@
-$ = jQuery
-context = window
-context.JK ||= {};
-
-context.JK.MusicianSearchFilter = class MusicianSearchFilter
-
- constructor: () ->
- @rest = context.JK.Rest()
- @logger = context.JK.logger
- @searchFilter = null
- @profileUtils = context.JK.ProfileUtils
- @helpBubble = context.JK.HelpBubbleHelper
- @searchResults = null
- @isSearching = false
- @pageNumber = 1
- @instrument_logo_map = context.JK.getInstrumentIconMap24()
- @instrumentSelector = null
-
- init: (app) =>
- @app = app
- @screenBindings = { 'afterShow': this.afterShow, 'afterHide': this.afterHide }
- @app.bindScreen('musicians', @screenBindings)
-
- @screen = $('#musicians-screen')
- @resultsListContainer = @screen.find('#musician-search-filter-results-list')
- @spinner = @screen.find('.paginate-wait')
- @instrumentSelector = new context.JK.InstrumentSelector(JK.app)
- @instrumentSelector.initialize(false, true)
-
- this.registerResultsPagination()
-
- @screen.find('#btn-musician-search-builder').on 'click', =>
- this.showBuilder()
- false
-
- @screen.find('#btn-musician-search-reset').on 'click', =>
- this.resetFilter()
- false
-
- afterShow: () =>
- @screen.find('#musician-search-filter-results').show()
- @screen.find('#musician-search-filter-builder').hide()
- this.getUserFilterResults()
-
- showBuilder: () =>
- @screen.find('#musician-search-filter-results').hide()
- @screen.find('#musician-search-filter-builder').show()
- @resultsListContainer.empty()
-
- afterHide: () =>
- @resultsListContainer.empty()
-
- renderSearchFilter: () =>
- $.when(this.rest.getMusicianSearchFilter()).done (sFilter) =>
- this.loadSearchFilter(sFilter)
-
- loadSearchFilter: (sFilter) =>
- @searchFilter = JSON.parse(sFilter)
- args =
- interests: @searchFilter.data_blob.interests
- skill_level: @searchFilter.data_blob.skill_level
- studio_sessions: @searchFilter.data_blob.studio_sessions
- concert_gigs: @searchFilter.data_blob.concert_gigs
-
- template = context.JK.fillTemplate(@screen.find('#template-musician-search-filter').html(), args)
-
- content_root = @screen.find('#musician-search-filter-builder')
- content_root.html template
-
- @screen.find('#btn-perform-musician-search').on 'click', =>
- this.performSearch()
- false
-
- @screen.find('#btn-musician-search-cancel').on 'click', =>
- this.cancelFilter()
- false
-
- this._populateSkill()
- this._populateStudio()
- this._populateGigs()
- this._populateInterests()
- this._populateAges()
- this._populateGenres()
- this._populateInstruments()
- this._populateSortOrder()
-
-
-
- _populateSelectWithKeys: (struct, selection, keys, element) =>
- element.children().remove()
- $.each keys, (idx, value) =>
- label = struct[value]
- blankOption = $ ''
- blankOption.text label
- blankOption.attr 'value', value
- element.append(blankOption)
- element.val(selection)
- context.JK.dropdown(element)
-
- _populateSelectIdentifier: (identifier) =>
- elem = $ '#musician-search-filter-builder select[name='+identifier+']'
- struct = gon.musician_search_meta[identifier]['map']
- keys = gon.musician_search_meta[identifier]['keys']
- console.log("@searchFilter", @searchFilter, identifier)
- this._populateSelectWithKeys(struct, @searchFilter.data_blob[identifier], keys, elem)
-
- _populateSelectWithInt: (sourceStruct, selection, element) =>
- struct =
- '-1': 'Any'
- $.extend(struct, sourceStruct)
- this._populateSelectWithKeys(struct, selection, Object.keys(struct).sort(), element)
-
- _populateSortOrder: () =>
- this._populateSelectIdentifier('sort_order')
-
- _populateInterests: () =>
- this._populateSelectIdentifier('interests')
-
- _populateStudio: () =>
- elem = $ '#musician-search-filter-builder select[name=studio_sessions]'
- this._populateSelectWithInt(@profileUtils.studioMap, @searchFilter.data_blob.studio_sessions.toString(), elem)
-
- _populateGigs: () =>
- elem = $ '#musician-search-filter-builder select[name=concert_gigs]'
- this._populateSelectWithInt(@profileUtils.gigMap, @searchFilter.data_blob.concert_gigs.toString(), elem)
-
- _populateSkill: () =>
- elem = $ '#musician-search-filter-builder select[name=skill_level]'
- this._populateSelectWithInt(@profileUtils.skillLevelMap, @searchFilter.data_blob.skill_level.toString(), elem)
-
- _populateAges: () =>
- @screen.find('#search-filter-ages').empty()
- ages_map = gon.musician_search_meta['ages']['map']
- $.each gon.musician_search_meta['ages']['keys'], (index, key) =>
- ageTemplate = @screen.find('#template-search-filter-setup-ages').html()
- ageLabel = ages_map[key]
- if 0 < @searchFilter.data_blob.ages.length
- key_val = key.toString()
- ageMatch = $.grep(@searchFilter.data_blob.ages, (n, i) ->
- n == key_val)
- selected = 'checked' if ageMatch.length > 0
- ageHtml = context._.template(ageTemplate,
- { id: key
- description: ageLabel
- checked: selected},
- {variable: 'data'})
- @screen.find('#search-filter-ages').append ageHtml
-
- _populateGenres: () =>
- @screen.find('#search-filter-genres').empty()
- @rest.getGenres().done (genres) =>
- genreTemplate = @screen.find('#template-search-filter-setup-genres').html()
- $.each genres, (index, genre) =>
- if 0 < @searchFilter.data_blob.genres.length
- genreMatch = $.grep(@searchFilter.data_blob.genres, (n, i) ->
- n == genre.id)
- else
- genreMatch = []
- selected = 'checked' if genreMatch.length > 0
- genreHtml = context._.template(genreTemplate,
- { id: genre.id
- description: genre.description
- checked: selected },
- { variable: 'data' })
- @screen.find('#search-filter-genres').append genreHtml
-
- _populateInstruments: () =>
-
- # TODO hydrate user's selection from json_store
- @instrumentSelector.render(@screen.find('.session-instrumentlist'), [])
- @instrumentSelector.setSelectedInstruments(@searchFilter.data_blob.instruments)
-
-
- _builderSelectValue: (identifier) =>
- elem = $ '#musician-search-filter-builder select[name='+identifier+']'
- elem.val()
-
- _builderSelectMultiValue: (identifier) =>
- vals = []
- elem = $ '#search-filter-'+identifier+' input[type=checkbox]:checked'
- if 'instruments' == identifier
- vals = @instrumentSelector.getSelectedInstruments()
- else
- elem.each (idx) ->
- vals.push $(this).val()
- vals
-
- willSearch: (reload) =>
- return false if @isSearching
- @isSearching = true
- if reload
- @pageNumber = 1
- @screen.find('#musician-search-filter-spinner').show()
- @resultsListContainer.empty()
- @screen.find('#musician-search-filter-builder').hide()
- @screen.find('#musician-search-filter-results').show()
- true
-
- didSearch: (response) =>
- this.loadSearchFilter(response.filter_json)
- @searchResults = response
- @screen.find('#musician-search-filter-spinner').hide()
- this.renderMusicians()
- @screen.find('.paginate-wait').hide()
- @isSearching = false
-
- resetFilter: () =>
- if this.willSearch(true)
- @rest.postMusicianSearchFilter({ filter: 'reset' }).done(this.didSearch)
-
- cancelFilter: () =>
- this.resetFilter()
-
- getUserFilterResults: () =>
- if this.willSearch(true)
- @rest.getMusicianSearchFilter('results=true').done(this.didSearch)
-
- performSearch: () =>
- if this.willSearch(true)
- filter = {}
- $.each gon.musician_search_meta.filter_keys.single, (index, key) =>
- filter[key] = this._builderSelectValue(key)
- $.each gon.musician_search_meta.filter_keys.multi, (index, key) =>
- filter[key] = this._builderSelectMultiValue(key)
- @rest.postMusicianSearchFilter({ filter: JSON.stringify(filter), page: @pageNumber }).done(this.didSearch)
-
- renderResultsHeader: () =>
- if @searchResults.is_blank_filter
- @screen.find('#btn-musician-search-reset').hide()
- @screen.find('.musician-search-text').text('Click search button to look for musicians with similar interests, skill levels, etc.')
- else
- @screen.find('#btn-musician-search-reset').show()
- @screen.find('.musician-search-text').text(@searchResults.summary)
-
- renderMusicians: () =>
- this.renderResultsHeader() if @pageNumber == 1
- musicians = @searchResults.musicians
- len = musicians.length
- if 0 == len
- @screen.find('#musician-search-filter-results-list-blank').show()
- @screen.find('#musician-search-filter-results-list-blank').html('No results found')
- return
- else
- @screen.find('#musician-search-filter-results-list-blank').hide()
-
- ii = 0
- mTemplate = @screen.find('#template-search-musician-row').html()
- aTemplate = @screen.find('#template-search-musician-action-btns').html()
- mVals = undefined
- musician = undefined
- renderings = ''
- instr_logos = undefined
- follows = undefined
- followVals = undefined
- aFollow = undefined
- myAudioLatency = @searchResults.my_audio_latency
- while ii < len
- musician = musicians[ii]
- if context.JK.currentUserId == musician.id
- ii++
- continue
- instr_logos = ''
- jj = 0
- ilen = musician['instruments'].length
- while jj < ilen
- instr_id = musician['instruments'][jj].instrument_id
- if instr_img = @instrument_logo_map[instr_id]
- instr_logos += '
'
- jj++
- actionVals =
- profile_url: '/client#/profile/' + musician.id
- friend_class: 'button-' + (if musician['is_friend'] then 'grey' else 'orange')
- friend_caption: (if musician.is_friend then 'DIS' else '') + 'CONNECT'
- follow_class: 'button-' + (if musician['is_following'] then 'grey' else 'orange')
- follow_caption: (if musician.is_following then 'UN' else '') + 'FOLLOW'
- message_class: 'button-orange'
- message_caption: 'MESSAGE'
- button_message: 'button-orange'
- musician_actions = context.JK.fillTemplate(aTemplate, actionVals)
- latencyBadge = context._.template($("#template-account-session-latency").html(), $.extend(sessionUtils.createLatency(musician), musician), variable: 'data')
- mVals =
- avatar_url: context.JK.resolveAvatarUrl(musician.photo_url)
- profile_url: '/client#/profile/' + musician.id
- musician_name: musician.name
- musician_location: this._formatLocation(musician)
- instruments: instr_logos
- biography: musician['biography']
- follow_count: musician['follow_count']
- friend_count: musician['friend_count']
- recording_count: musician['recording_count']
- session_count: musician['session_count']
- musician_id: musician['id']
- musician_action_template: musician_actions
- latency_badge: latencyBadge
- musician_first_name: musician['first_name']
- $rendering = $(context.JK.fillTemplate(mTemplate, mVals))
- $offsetParent = @resultsListContainer.closest('.content')
- data = entity_type: 'musician'
- options =
- positions: [
- 'top'
- 'bottom'
- 'right'
- 'left'
- ]
- offsetParent: $offsetParent
- scoreOptions = offsetParent: $offsetParent
- context.JK.helpBubble($('.follower-count', $rendering), 'follower-count', data, options);
- context.JK.helpBubble($('.friend-count', $rendering), 'friend-count', data, options);
- context.JK.helpBubble($('.recording-count', $rendering), 'recording-count', data, options);
- context.JK.helpBubble($('.session-count', $rendering), 'session-count', data, options);
- @helpBubble.scoreBreakdown $('.latency', $rendering), false, musician['full_score'], myAudioLatency, musician['audio_latency'], musician['score'], scoreOptions
- @resultsListContainer.append $rendering
- $rendering.find('.biography').dotdotdot()
- ii++
-
- this._bindMessageMusician()
- this._bindFriendMusician()
- this._bindFollowMusician()
-
- context.JK.bindHoverEvents()
- return
-
- _bindMessageMusician: () =>
- objThis = this
- @screen.find('.search-m-message').on 'click', (evt) ->
- userId = $(this).parent().data('musician-id')
- objThis.app.layout.showDialog 'text-message', d1: userId
- return false
-
-
- _bindFriendMusician: () =>
- objThis = this
- @screen.find('.search-m-friend').on 'click', (evt) =>
- # if the musician is already a friend, remove the button-orange class, and prevent the link from working
- $self = $(evt.target)
- if 0 == $self.closest('.button-orange').size()
- return false
- logger.debug("evt.target", evt.target)
- $self.click (ee) ->
- ee.preventDefault()
- return false
- evt.stopPropagation()
- uid = $self.parent().data('musician-id')
- objThis.rest.sendFriendRequest objThis.app, uid, this.friendRequestCallback
- @app.notify({text: 'A friend request has been sent.'})
- return false
-
- _bindFollowMusician: () =>
- objThis = this
- @screen.find('.search-m-follow').on 'click', (evt) ->
- # if the musician is already followed, remove the button-orange class, and prevent the link from working
- if 0 == $(this).closest('.button-orange').size()
- return false
- $(this).click (ee) ->
- ee.preventDefault()
- return false
- evt.stopPropagation()
- newFollowing = {}
- newFollowing.user_id = $(this).parent().data('musician-id')
- url = '/api/users/' + context.JK.currentUserId + '/followings'
- $.ajax
- type: 'POST'
- dataType: 'json'
- contentType: 'application/json'
- url: url
- data: JSON.stringify(newFollowing)
- processData: false
- success: (response) ->
- # remove the orange look to indicate it's not selectable
- # @FIXME -- this will need to be tweaked when we allow unfollowing
- objThis.screen.find('div[data-musician-id=' + newFollowing.user_id + '] .search-m-follow').removeClass('button-orange').addClass 'button-grey'
- return false
- error: objThis.app.ajaxError
- return false
-
- _formatLocation: (musician) ->
- if musician.city and musician.state
- musician.city + ', ' + musician.state
- else if musician.city
- musician.city
- else if musician.regionname
- musician.regionname
- else
- 'Location Unavailable'
-
- friendRequestCallback: (user_id)=>
- # TODO:
-
- paginate: () =>
-
- if @pageNumber < @searchResults.page_count && this.willSearch(false)
- @screen.find('.paginate-wait').show()
- @pageNumber += 1
- @rest.postMusicianSearchFilter({ filter: JSON.stringify(@searchFilter.data_blob), page: @pageNumber }).done(this.didSearch)
- return true
- false
-
- registerResultsPagination: () =>
- _resultsListContainer = @resultsListContainer
- _headerHeight = @screen.find('#musician-search-filter-results-header').height()
- _paginator = this.paginate
-
- @screen.find('.content-body-scroller').scroll ->
- if _resultsListContainer.is(':visible')
- jthis = $(this)
- wintop = jthis.scrollTop()
- winheight = jthis.innerHeight()
- docheight = jthis[0].scrollHeight - _headerHeight
- scrollTrigger = 0.98;
- if ((wintop / (docheight - winheight)) >= scrollTrigger)
- _paginator()
diff --git a/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee
index aa7654c7e..afd9567da 100644
--- a/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/SessionMetronome.js.jsx.coffee
@@ -32,6 +32,7 @@ MIX_MODES = context.JK.MIX_MODES
componentClasses = classNames({
"session-track" : true
"metronome" : true
+ "in-jam-track" : @props.location == 'jam-track'
"no-mixer" : @props.mode == MIX_MODES.MASTER # show it as disabled if in master mode
"in-jam-track" : @props.location == 'jam-track'
})
diff --git a/web/app/assets/javascripts/webcam_viewer.js.coffee b/web/app/assets/javascripts/webcam_viewer.js.coffee
index 87e6a7f93..a5538167a 100644
--- a/web/app/assets/javascripts/webcam_viewer.js.coffee
+++ b/web/app/assets/javascripts/webcam_viewer.js.coffee
@@ -32,7 +32,9 @@ context.JK.WebcamViewer = class WebcamViewer
this.loadResolutions()
this.selectResolution()
@initialScan = true
- @client.SessStopVideoSharing()
+ # protect against non-video clients pointed at video-enabled server from getting into a session
+ if @client.SessStopVideoSharing
+ @client.SessStopVideoSharing()
#client.SessSetInsetPosition(5)
#client.SessSetInsetSize(1)
#client.FTUESetAutoSelectVideoLayout(false)
@@ -82,14 +84,16 @@ context.JK.WebcamViewer = class WebcamViewer
selectedDeviceName:() =>
webcamName="None Configured"
- webcam = @client.FTUECurrentSelectedVideoDevice()
+ # protect against non-video clients pointed at video-enabled server from getting into a session
+ webcam = if @client.FTUECurrentSelectedVideoDevice? then @client.FTUECurrentSelectedVideoDevice() else null
if (webcam? && Object.keys(webcam).length>0)
webcamName = _.values(webcam)[0]
webcamName
loadWebCams:() =>
- devices = @client.FTUEGetVideoCaptureDeviceNames()
+ # protect against non-video clients pointed at video-enabled server from getting into a session
+ devices = if @client.FTUEGetVideoCaptureDeviceNames? then @client.FTUEGetVideoCaptureDeviceNames() else []
selectedDevice = this.selectedDeviceName()
selectControl = @webcamSelect
context._.each devices, (device) ->
@@ -107,7 +111,8 @@ context.JK.WebcamViewer = class WebcamViewer
@root.find('.no-webcam-msg').addClass 'hidden'
loadResolutions:() =>
- resolutions = @client.FTUEGetAvailableEncodeVideoResolutions()
+ # protect against non-video clients pointed at video-enabled server from getting into a session
+ resolutions = if @client.FTUEGetAvailableEncodeVideoResolutions? then @client.FTUEGetAvailableEncodeVideoResolutions() else {}
selectControl = @resolutionSelect
@logger.debug 'FOUND THESE RESOLUTIONS', resolutions, selectControl
context._.each resolutions, (value, key, obj) ->
diff --git a/web/app/assets/stylesheets/client/band.css.scss b/web/app/assets/stylesheets/client/band.css.scss
index e3b33b7a3..2cbdc6b26 100644
--- a/web/app/assets/stylesheets/client/band.css.scss
+++ b/web/app/assets/stylesheets/client/band.css.scss
@@ -1,6 +1,6 @@
@import "client/common.css.scss";
-#band-setup, #band-profile {
+#band-setup, #band-profile, #bands-filter-to_hire, #bands-filter-to_join {
font-family: Raleway, Arial, Helvetica, verdana, arial, sans-serif;
.band-field {
input[type="text"], select, textarea {
@@ -495,10 +495,178 @@
label {
font-size: 1.05em;
}
+}
- label.strong-label {
- font-weight: bold;
- font-size: 1.1em;
+#bands-screen {
+
+ .col-left {
+ float: left;
+ width: 50%;
+ margin-left: auto;
+ margin-right: auto;
}
+ .col-right {
+ float: right;
+ width: 50%;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ #band-search-filter-spinner {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ margin-top: 40px;
+ }
+
+ #band-search-filter-results-list {
+ .profile-band-list-result {
+ padding-top: 5px;
+ }
+ }
+
+ .builder-section {
+ padding: 10px;
+ margin-bottom: 20px;
+ }
+
+ .builder-sort-order {
+ .text-label {
+ float: right;
+ margin-right: 10px;
+ padding-top: 5px;
+ }
+ .sort-order-selector {
+ float: right;
+ .easydropdown-wrapper {
+ width: 140px;
+ }
+ }
+ }
+
+ #band-search-filter-builder {
+ margin-left: 5px;
+ }
+
+ .band-search-filter-builder-bottom {
+ text-align: right;
+ #btn-perform-band-search {
+ width: 120px;
+ margin-left: 20px;
+ }
+ }
+
+ tr:nth-child(even) td {
+ padding-bottom: 0;
+ }
+
+ #band-search-filter-results-header {
+ padding: 10px 10px 10px 10px;
+ }
+
+ #band-search-filter-description {
+ padding: 5px 5px 5px 5px;
+ display: inline;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ #btn-band-search-builder {
+ float: left;
+ margin-right: 20px;
+ }
+
+ #btn-band-search-reset {
+ float: right;
+ margin-left: 20px;
+ }
+
+ #bands-filter-to_hire {
+ width: 100%;
+
+ .builder-selector {
+ margin-top: 10px;
+ }
+
+ #max_cost_amount {
+ width: 60px;
+ }
+
+ .col-left {
+ float: left;
+ width: 45%;
+ margin-left: auto;
+ margin-right: auto;
+ padding-right: 20px;
+
+ }
+
+ .col-right {
+ float: right;
+ width: 45%;
+ margin-left: auto;
+ margin-right: auto;
+
+ .easydropdown-wrapper {
+ width: 100%;
+ }
+ }
+ }
+
+ #bands-filter-to_join {
+ width: 100%;
+
+ .builder-selector {
+ margin-top: 10px;
+ }
+ .lhs {
+ float: left;
+ width: 40%;
+ .easydropdown-wrapper {
+ width: 100%;
+ }
+ }
+ .rhs {
+ float: right;
+ width: 40%;
+ .easydropdown-wrapper {
+ width: 100%;
+ }
+ }
+ .col-left {
+ float: left;
+ width: 30%;
+ margin-left: auto;
+ margin-right: auto;
+ padding-right: 20px;
+
+ .easydropdown-wrapper {
+ width: 100%;
+ }
+
+ }
+
+ .col-right {
+ float: right;
+ width: 66%;
+ margin-left: auto;
+ margin-right: auto;
+
+ }
+
+ .search-filter-setup-genres {
+ }
+
+ .band-setup-genres {
+ .easydropdown-wrapper {
+ width: 100%;
+ }
+ }
+ .builder-section {
+ padding: 10px;
+ margin-bottom: 20px;
+ }
+
+ }
}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/client/musician.css.scss b/web/app/assets/stylesheets/client/musician.css.scss
index 7fcd81f54..69f9b82fe 100644
--- a/web/app/assets/stylesheets/client/musician.css.scss
+++ b/web/app/assets/stylesheets/client/musician.css.scss
@@ -254,15 +254,15 @@
.col-left {
@include border_box_sizing;
float: left;
- width: 33%;
+ width: 45%;
margin-left: auto;
margin-right: auto;
+ padding-right: 20px;
}
.col-right {
float: right;
- width: 67%;
- @include border_box_sizing;
+ width: 45%;
margin-left: auto;
margin-right: auto;
@@ -306,6 +306,14 @@
}
.builder-ages {
width: 100%;
+ height: 100%;
+ background-color:#c5c5c5;
+ border:none;
+ -webkit-box-shadow: inset 2px 2px 3px 0px #888;
+ box-shadow: inset 2px 2px 3px 0px #888;
+ color:#000;
+ overflow:auto;
+ font-size:12px;
}
.builder-instruments {
width: 100%;
@@ -321,6 +329,21 @@
}
}
+ .musician-setup-genres {
+ width:100%;
+ height:200px;
+ background-color:#c5c5c5;
+ border:none;
+ -webkit-box-shadow: inset 2px 2px 3px 0px #888;
+ box-shadow: inset 2px 2px 3px 0px #888;
+ color:#000;
+ overflow:auto;
+ font-size:12px;
+ .easydropdown-wrapper {
+ width: 100%;
+ }
+ }
+
}
.filter-element {
diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss
index 49529aed6..82614b21e 100644
--- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss
+++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss
@@ -175,6 +175,17 @@
.track-controls {
margin-left:0;
}
+
+ &.in-jam-track {
+ min-height:56px;
+
+ .track-buttons {
+ margin-top:2px;
+ }
+ table.vu {
+ margin-top: 10px;
+ }
+ }
}
&.recorded-track, &.jam-track, &.recorded-category, &.jam-track-category {
diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb
index f1beaa5f9..c7433ea5a 100644
--- a/web/app/controllers/api_music_sessions_controller.rb
+++ b/web/app/controllers/api_music_sessions_controller.rb
@@ -482,7 +482,7 @@ class ApiMusicSessionsController < ApiController
comment.save
if comment.errors.any?
- render :json => { :message => "Unexpected error occurred" }, :status => 500
+ render :json => { :errors => comment.errors }, :status => 422
return
else
render :json => {}, :status => 201
@@ -508,7 +508,7 @@ class ApiMusicSessionsController < ApiController
comment.save
if comment.errors.any?
- render :json => { :message => "Unexpected error occurred" }, :status => 500
+ render :json => { :errors => comment.errors }, :status => 422
return
else
music_session = MusicSession.find(params[:id])
diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb
index b4012be5d..0767d9053 100644
--- a/web/app/controllers/api_recordings_controller.rb
+++ b/web/app/controllers/api_recordings_controller.rb
@@ -155,7 +155,7 @@ class ApiRecordingsController < ApiController
comment.save
if comment.errors.any?
- render :json => { :message => "Unexpected error occurred" }, :status => 500
+ render :json => { :errors => comment.errors }, :status => 422
return
else
render :json => {}, :status => 201
@@ -178,7 +178,7 @@ class ApiRecordingsController < ApiController
liker.save
if liker.errors.any?
- render :json => { :message => "Unexpected error occurred" }, :status => 500
+ render :json => { :errors => liker.errors }, :status => 422
return
else
render :json => {}, :status => 201
diff --git a/web/app/controllers/api_search_controller.rb b/web/app/controllers/api_search_controller.rb
index 0663265ff..54e3ab5b0 100644
--- a/web/app/controllers/api_search_controller.rb
+++ b/web/app/controllers/api_search_controller.rb
@@ -7,7 +7,7 @@ class ApiSearchController < ApiController
def index
if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i
- query = params.clone
+ query = parasobj.clone
query[:remote_ip] = request.remote_ip
if 1 == query[Search::PARAM_MUSICIAN].to_i
@search = Search.musician_filter(query, current_user)
@@ -32,13 +32,35 @@ class ApiSearchController < ApiController
end
elsif request.post?
- ms = MusicianSearch.user_search_filter(current_user)
+ sobj = MusicianSearch.user_search_filter(current_user)
filter = params[:filter]
if filter == 'reset'
- @search = ms.reset_search_results
+ @search = sobj.reset_search_results
else
json = JSON.parse(filter, :create_additions => false)
- @search = ms.search_results_page(json, [params[:page].to_i, 1].max)
+ @search = sobj.search_results_page(json, [params[:page].to_i, 1].max)
+ end
+ respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index'
+ end
+ end
+
+ def bands
+ if request.get?
+ if params[:results]
+ @search = BandSearch.user_search_filter(current_user).search_results_page(params[:subtype])
+ respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index'
+ else
+ render :json => BandSearch.search_filter_json(current_user, params[:subtype]), :status => 200
+ end
+
+ elsif request.post?
+ sobj = BandSearch.user_search_filter(current_user)
+ filter = params[:filter]
+ if filter == 'reset'
+ @search = sobj.reset_search_results(params[:subtype])
+ else
+ json = JSON.parse(filter, :create_additions => false)
+ @search = sobj.search_results_page(params[:subtype], json, [params[:page].to_i, 1].max)
end
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/index'
end
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index 5c8f27377..18c2db8f8 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -796,7 +796,7 @@ class ApiUsersController < ApiController
play.save
if play.errors.any?
- render :json => { :message => "Unexpected error occurred" }, :status => 500
+ render :json => { :errors => play.errors }, :status => 422
else
render :json => {}, :status => 201
end
diff --git a/web/app/controllers/spikes_controller.rb b/web/app/controllers/spikes_controller.rb
index 0edce85ce..069a2d757 100644
--- a/web/app/controllers/spikes_controller.rb
+++ b/web/app/controllers/spikes_controller.rb
@@ -69,7 +69,12 @@ class SpikesController < ApplicationController
end
def musician_search_filter
- # gon.musician_search_meta = MusicianSearch::SEARCH_FILTER_META
+ # gon.musician_search_meta = MusicianSearch.search_filter_meta
+ render :layout => 'web'
+ end
+
+ def band_search_filter
+ gon.band_search_meta = BandSearch.search_filter_meta
render :layout => 'web'
end
diff --git a/web/app/helpers/client_helper.rb b/web/app/helpers/client_helper.rb
index c4977e130..f6febfb1d 100644
--- a/web/app/helpers/client_helper.rb
+++ b/web/app/helpers/client_helper.rb
@@ -67,7 +67,8 @@ module ClientHelper
gon.ftue_network_test_duration = Rails.application.config.ftue_network_test_duration
gon.ftue_network_test_max_clients = Rails.application.config.ftue_network_test_max_clients
gon.ftue_maximum_gear_latency = Rails.application.config.ftue_maximum_gear_latency
- gon.musician_search_meta = MusicianSearch::SEARCH_FILTER_META
+ gon.musician_search_meta = MusicianSearch.search_filter_meta
+ gon.band_search_meta = BandSearch.search_filter_meta
# is this the native client or browser?
@nativeClient = is_native_client?
diff --git a/web/app/views/api_jam_tracks/show.rabl b/web/app/views/api_jam_tracks/show.rabl
index 028180632..e05ca0cbf 100644
--- a/web/app/views/api_jam_tracks/show.rabl
+++ b/web/app/views/api_jam_tracks/show.rabl
@@ -3,7 +3,7 @@ object @jam_track
attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version, :duration
node :genres do |item|
- [item.genre.description] # XXX: need to return single genre; not array
+ item.genres.select(:description).map(&:description)
end
node :added_cart do |item|
diff --git a/web/app/views/api_jam_tracks/show_for_client.rabl b/web/app/views/api_jam_tracks/show_for_client.rabl
index bc212f46d..ce60ba00b 100644
--- a/web/app/views/api_jam_tracks/show_for_client.rabl
+++ b/web/app/views/api_jam_tracks/show_for_client.rabl
@@ -1,9 +1,9 @@
object @jam_track
-attributes :id, :name, :description, :initial_play_silence, :original_artist, :version, :genre
+attributes :id, :name, :description, :initial_play_silence, :original_artist, :version
-node :genre do |jam_track|
- jam_track.genre.present? ? jam_track.genre.id : nil
+node :genres do |item|
+ item.genres.select(:description).map(&:description)
end
node :jmep do |jam_track|
diff --git a/web/app/views/api_search/index.rabl b/web/app/views/api_search/index.rabl
index 4296eb164..12e7fbf53 100644
--- a/web/app/views/api_search/index.rabl
+++ b/web/app/views/api_search/index.rabl
@@ -1,8 +1,6 @@
object @search
-node :search_type do |ss| ss.search_type end
-
-if @search.is_a?(MusicianSearch)
+if @search.is_a?(BaseSearch)
node :page_count do |foo|
@search.page_count
@@ -12,18 +10,19 @@ if @search.is_a?(MusicianSearch)
current_user.last_jam_audio_latency.round if current_user.last_jam_audio_latency
end
- node :is_blank_filter do |foo|
- @search.is_blank?
- end
-
node :filter_json do |foo|
@search.to_json
end
- node :summary do |foo|
- @search.description
- end
+ if @search.is_a? MusicianSearch
+ node :description do |foo|
+ @search.description
+ end
+ node :is_blank_filter do |foo|
+ @search.is_blank?
+ end
+
child(:results => :musicians) {
attributes :id, :first_name, :last_name, :name, :city, :state, :country, :online, :musician, :photo_url, :biography, :regionname, :score, :full_score
@@ -53,8 +52,8 @@ if @search.is_a?(MusicianSearch)
node :name do |uu| uu.name end
end
- node :follow_count do |musician| @search.follow_count(musician) end
node :friend_count do |musician| @search.friend_count(musician) end
+ node :follow_count do |musician| @search.follow_count(musician) end
node :recording_count do |musician| @search.record_count(musician) end
node :session_count do |musician| @search.session_count(musician) end
@@ -62,8 +61,49 @@ if @search.is_a?(MusicianSearch)
last_jam_audio_latency(musician)
end
}
+
+elsif @search.is_a?(BandSearch)
+
+ node :is_blank_filter do |foo|
+ @search.is_blank?(params[:subtype])
+ end
+
+ node :description do |foo|
+ @search.description(params[:subtype])
+ end
+
+ child(:results => :bands) {
+ attributes :id, :name, :city, :state, :country, :photo_url, :biography, :logo_url, :website
+
+ node :is_following do |band|
+ @search.is_follower?(band)
+ end
+
+ node :biography do |band|
+ band.biography.nil? ? "" : band.biography
+ end
+
+ child :genres => :genres do
+ attributes :genre_id, :description
+ end
+
+ child :users => :players do |pl|
+ node :user_id do |uu| uu.id end
+ node :photo_url do |uu| uu.photo_url end
+ node :name do |uu| uu.name end
+ node :instruments do |uu| uu.instruments.map(&:id).join(',') end
+ end
+
+ node :follow_count do |band| @search.follow_count(band) end
+ node :recording_count do |band| @search.record_count(band) end
+ node :session_count do |band| @search.session_count(band) end
+ }
+end
+
else
+node :search_type do |ss| ss.search_type end
+
if @search.session_invite_search?
child(:results => :suggestions) {
node :value do |uu| uu.name end
diff --git a/web/app/views/clients/_band_search_filter.html.slim b/web/app/views/clients/_band_search_filter.html.slim
new file mode 100644
index 000000000..58340e5d1
--- /dev/null
+++ b/web/app/views/clients/_band_search_filter.html.slim
@@ -0,0 +1,216 @@
+.content-body-scroller
+ div#band-search-filter-builder.content-wrapper
+
+ div#band-search-filter-results.content-wrapper
+ div#band-search-filter-results-header
+ #band-search-filter-results-blank
+ a.button-orange#btn-band-search-builder-to_join href="#" SEARCH FOR BAND TO JOIN
+ | - or -
+ a.button-orange#btn-band-search-builder-to_hire href="#" SEARCH FOR BAND TO HIRE
+ #band-search-filter-results-filtered
+ a.button-orange#btn-band-search-builder href="#" SEARCH
+ a.button-grey#btn-band-search-reset href="#" RESET
+ div#band-search-filter-description
+ div.clearall
+ div#band-search-filter-spinner.spinner-large
+
+ div#band-search-filter-results-wrapper
+ div#band-search-filter-results-list-blank
+ div.content-wrapper#band-search-filter-results-list
+ div.paginate-wait
+ Fetching more results...
+ div.spinner-small
+
+script#template-band-search-filter-to_join type="text/template"
+ #bands-filter-to_join
+ .band-search-filter-builder-top.builder-section
+ .col-left
+ h2 search bands
+ / .col-right.builder-sort-order
+ / .sort-order-selector
+ / select.easydropdown name="sort_order"
+ / option selected="selected" value="{sort_order}" {sort_order}
+ / .text-label Sort Results By:
+ .clearall
+
+ .band-search-filter-builder-middle1.builder-section
+ .col-left
+ .field
+ label for="search-filter-genres" Genres:
+ .search-filter-setup-genres.band-setup-genres
+ table#search-filter-genres cellpadding="10" cellspacing="6" width="100%"
+
+ .col-right
+ .field
+ label for="search-filter-instruments"
+ | Looking for New Members with These Skills:
+ .search-filter-setup-instruments.band-setup-genres.builder-instruments
+ table#search-filter-instruments cellpadding="10" cellspacing="6" width="100%"
+
+ .clearall
+
+ .band-search-filter-builder-middle2.builder-section
+ .col-left
+ .field.builder-selector
+ label Type:
+ select.easydropdown name="band_type"
+ option selected="selected" value="{band_type}" {band_type}
+
+ .field.builder-selector
+ label Play Commitment:
+ select.easydropdown name="play_commitment"
+ option selected="selected" value="{play_commitment}" {play_commitment}
+
+ .col-right
+ .field.builder-selector
+ .lhs
+ label Status:
+ select.easydropdown name="band_status"
+ option selected="selected" value="{band_status}" {band_status}
+
+ .field.builder-selector
+ .rhs
+ label Concert Gigs Played:
+ select.easydropdown name="concert_gigs"
+ option selected="selected" value="{concert_gigs}" {concert_gigs}
+
+ .clearall
+
+ .field.builder-selector
+ .lhs
+ label Touring Option:
+ select.easydropdown name="touring_option"
+ option selected="selected" value="{touring_option}" {touring_option}
+ .clearall
+
+ .clearall
+ .band-search-filter-builder-bottom.builder-section.builder-action-buttons
+ .col-right
+ a#btn-band-search-cancel.builder-button.button-grey href="#" CANCEL
+ a#btn-perform-band-search.builder-button.button-orange href="#" SEARCH
+
+script#template-band-search-filter-to_hire type="text/template"
+ #bands-filter-to_hire
+ .band-search-filter-builder-top-to_hire.builder-section
+ .col-left
+ h2 search bands to hire
+ .col-right.builder-sort-order
+ .sort-order-selector
+ select.easydropdown name="sort_order"
+ option selected="selected" value="{sort_order}" {sort_order}
+ .text-label Sort Results By:
+ .clearall
+
+ .band-search-filter-builder-middle1.builder-section
+ .col-left
+ .field
+ label for="search-filter-genres" Genres:
+ .search-filter-setup-genres.band-setup-genres
+ table#search-filter-genres cellpadding="10" cellspacing="6" width="100%"
+
+ .col-right
+ .field.builder-selector
+ label Status:
+ select.easydropdown name="band_status"
+ option selected="selected" value="{band_status}" {band_status}
+
+ .field.builder-selector
+ label Concert Gigs Played:
+ select.easydropdown name="concert_gigs"
+ option selected="selected" value="{concert_gigs}" {concert_gigs}
+
+ .field.builder-selector
+ label Performance Sample Available:
+ select.easydropdown name="performance_samples"
+ option selected="selected" value="{performance_samples}" {performance_samples}
+
+ .clearall
+
+ .band-search-filter-builder-middle2.builder-section
+ .field.builder-selector
+ Find bands to play a paid gig at a cost not to exceed $
+ input type="text" id="max_cost_amount" name="max_cost" value="{max_cost}"
+
+ .field.builder-selector
+ Find bands that will play free gigs
+
+ .clearall
+
+ .clearall
+ .band-search-filter-builder-bottom.builder-section.builder-action-buttons
+ .col-right
+ a#btn-band-search-cancel.builder-button.button-grey href="#" CANCEL
+ a#btn-perform-band-search.builder-button.button-orange href="#" SEARCH
+
+script#template-search-filter-setup-instrument type="text/template"
+ tr data-instrument-id="{id}"
+ td {description}
+ td align="right" width="50%"
+ select.proficiency_selector name="proficiency"
+ option value="1" Beginner
+ option value="2" Intermediate
+ option value="3" Expert
+
+script#template-search-filter-setup-genres type="text/template"
+ tr
+ td {description}
+
+script#template-find-band-row type="text/template"
+ .profile-band-list-result.band-list-result
+ .f11
+ .left style="width:63px;margin-top:-12px;"
+ .avatar-small
+ img src="{avatar_url}" /
+ .right.mband-players style="width:265px; margin-top:-4px;"
+ table.musicians cellpadding="0" cellspacing="5"
+ | {band_player_template}
+ div style="margin-left: 63px; margin-right: 275px;margin-top: 12px;"
+ .first-row data-hint="top-row"
+ .lcol.left
+ .result-name
+ | {band_name}
+ .result-location
+ | {band_location}
+ br
+ #result_genres.nowrap.mt10
+ | {genres}
+ .whitespace
+ .biography
+ | {biography}
+ .clearleft
+ .button-row
+ .lcol.stats.left
+ span.follower-count
+ | {follow_count}
+ img src="../assets/content/icon_followers.png" width="22" height="12" align="absmiddle" style="margin-right:4px;"
+ span.recording-count
+ | {recording_count}
+ img src="../assets/content/icon_recordings.png" width="12" height="13" align="absmiddle" style="margin-right:4px;"
+ span.session-count
+ | {session_count}
+ img src="../assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle"
+ .clearall
+ .result-list-button-wrapper data-band-id="{band_id}"
+ | {band_action_template}
+ .clearall
+
+script#template-band-action-btns type="text/template"
+ a.button-orange.smallbutton href="{profile_url}" PROFILE
+ a.smallbutton.search-m-follow href="#" class="{button_follow}" FOLLOW
+
+script#template-band-edit-btns type="text/template"
+ a.button-orange.smallbutton href="{profile_url}" PROFILE
+ a.button-orange.smallbutton href="{band_edit_url}" EDIT BAND
+ a.button-orange.smallbutton href="{band_member_url}" INVITE
+
+script#template-band-player-info type="text/template"
+ tr
+ td
+ a.avatar-tiny href="{profile_url}" user-id="{user_id}" hoveraction="musician"
+ img src="{avatar_url}"
+ td style="padding: 0 4px;width:88px;"
+ a user-id="{user_id}" hoveraction="musician" href="{profile_url}"
+ strong
+ | {player_name}
+ td.instruments
+ | {player_instruments}
diff --git a/web/app/views/clients/_bands.html.erb b/web/app/views/clients/_bands.html.erb
deleted file mode 100644
index ae7bdbf5c..000000000
--- a/web/app/views/clients/_bands.html.erb
+++ /dev/null
@@ -1,86 +0,0 @@
-
-<%= content_tag(:div, :layout => 'screen', 'layout-id' => 'bands', :class => "screen secondary", :id => "bands-screen") do -%>
- <%= content_tag(:div, :class => :content) do -%>
- <%= content_tag(:div, :class => 'content-head') do -%>
- <%= content_tag(:div, image_tag("content/icon_bands.png", {:height => 19, :width => 19}), :class => 'content-icon') %>
- <%= content_tag(:h1, 'bands') %>
- <%= render "screen_navigation" %>
- <% end -%>
- <%= content_tag(:div, :class => 'content-body') do -%>
- <%= form_tag('', {:id => 'find-band-form', :class => 'inner-content'}) do -%>
- <%= render(:partial => "web_filter", :locals => {:search_type => Search::PARAM_BAND}) %>
- <%= content_tag(:div, :class => 'filter-body') do %>
- <%= content_tag(:div, :class => 'content-body-scroller') do -%>
- <%= content_tag(:div, content_tag(:div, '', :id => 'band-filter-results', :class => 'filter-results'), :class => 'content-wrapper') %>
- <% end -%>
- <% end -%>
- <% end -%>
- <% end -%>
- <% end -%>
-<% end -%>
-
-
-
-
-
-
-
-
-
-
diff --git a/web/app/views/clients/_bands.html.slim b/web/app/views/clients/_bands.html.slim
new file mode 100644
index 000000000..c472fbe39
--- /dev/null
+++ b/web/app/views/clients/_bands.html.slim
@@ -0,0 +1,140 @@
+#bands-screen.screen.secondary layout="screen" layout-id="bands"
+ .content
+ .content-head
+ .content-icon
+ img alt="Icon_bands" height="19" src="/assets/content/icon_bands.png" width="19" /
+ h1 bands
+ = render "screen_navigation"
+ .content-body
+ = render "clients/band_search_filter"
+
+script#template-band-search-filter-to_join type="text/template"
+ #bands-filter-to_join
+ #band-search-filter-builder-top.builder-section
+ .col-left
+ h2 search bands
+ .col-right.builder-sort-order
+ .text-label Sort Results By:
+ select.easydropdown name="sort_order"
+ option selected="selected" value="{sort_order}" {sort_order}
+ .clearall
+
+ #band-search-filter-builder-middle1.builder-section
+ .col-left
+ .field
+ label for="search-filter-genres" Genres:
+ .search-filter-setup-genres.band-setup-genres
+ table#search-filter-genres cellpadding="10" cellspacing="6" width="100%"
+
+ .col-right
+ .field
+ label for="search-filter-instruments"
+ | Instruments & Skill Level:
+ .search-filter-setup-instruments.band-setup-genres.builder-instruments
+ table#search-filter-instruments cellpadding="10" cellspacing="6" width="100%"
+ .clearall
+
+ #band-search-filter-builder-middle2.builder-section
+ .col-left
+ .field.builder-selector
+ label Type:
+ select.easydropdown name="band_type"
+ option selected="selected" value="{band_type}" {band_type}
+
+ .field.builder-selector
+ label Play Commitment:
+ select.easydropdown name="play_commit"
+ option selected="selected" value="{play_commit}" {play_commit}
+
+ .col-right
+ .field.builder-selector
+ label Status:
+ select.easydropdown name="skill_level"
+ option selected="selected" value="{skill_level}" {skill_level}
+
+ .field.builder-selector
+ label Concert Gigs Played:
+ select.easydropdown name="concert_gigs"
+ option selected="selected" value="{concert_gigs}" {concert_gigs}
+
+ .field.builder-selector
+ label Touring Option:
+ select.easydropdown name="tour_option"
+ option selected="selected" value="{tour_option}" {tour_option}
+
+ .clearall
+
+ .clearall
+ #band-search-filter-builder-bottom.builder-section.builder-action-buttons
+ .col-right
+ a#btn-perform-band-search.builder-button.button-orange href="#" SEARCH
+ a#btn-band-search-cancel.builder-button.button-grey href="#" CANCEL
+
+/! Session Row Template
+script#template-search-band-row type="text/template"
+ .profile-band-list-result.band-list-result
+ .f11
+ .left style="width:63px;margin-top:-12px;"
+ /! avatar
+ .avatar-small
+ img src="{avatar_url}" /
+ .right.mband-players style="width:265px; margin-top:-4px;"
+ table.musicians cellpadding="0" cellspacing="5"
+ | {band_player_template}
+ div style="margin-left: 63px; margin-right: 275px;margin-top: 12px;"
+ .first-row data-hint="top-row"
+ .lcol.left
+ /! name and location
+ .result-name
+ | {band_name}
+ .result-location
+ | {band_location}
+ br /
+ #result_genres.nowrap.mt10
+ | {genres}
+ .whitespace
+ .biography
+ | {biography}
+ .clearleft
+ .button-row
+ .lcol.stats.left
+ span.follower-count
+ | {follow_count}
+ img src="../assets/content/icon_followers.png" width="22" height="12" align="absmiddle" style="margin-right:4px;" /
+ span.recording-count
+ | {recording_count}
+ img src="../assets/content/icon_recordings.png" width="12" height="13" align="absmiddle" style="margin-right:4px;" /
+ span.session-count
+ | {session_count}
+ img src="../assets/content/icon_session_tiny.png" width="12" height="12" align="absmiddle" /
+ .clearall
+ .result-list-button-wrapper data-band-id="{band_id}"
+ | {band_action_template}
+ .clearall
+
+script#template-search-band-action-btns type="text/template"
+ a.button-orange smallbutton href="{profile_url}"
+ PROFILE
+ a class="{button_follow} smallbutton search-m-follow" href="#"
+ FOLLOW
+
+script#template-search-band-edit-btns type="text/template"
+ a href="{profile_url}" class="button-orange smallbutton"
+ PROFILE
+ a href="{band_edit_url}" class="button-orange smallbutton"
+ EDIT BAND
+ a href="{band_member_url}" class="button-orange smallbutton"
+ INVITE
+
+script#template-search-band-player-info type="text/template"
+ tr
+ td
+ a.avatar-tiny href="{profile_url}" user-id="{user_id}" hoveraction="musician"
+ img src="{avatar_url}" /
+ td style="padding: 0 4px;width:88px"
+ a user-id="{user_id}" hoveraction="musician" href="{profile_url}"
+ strong
+ | {player_name}
+ td.instruments
+ | {player_instruments}
+
diff --git a/web/app/views/clients/_musician_search_filter.html.slim b/web/app/views/clients/_musician_search_filter.html.slim
index bbd9a949a..b454bee83 100644
--- a/web/app/views/clients/_musician_search_filter.html.slim
+++ b/web/app/views/clients/_musician_search_filter.html.slim
@@ -4,7 +4,6 @@
div#musician-search-filter-results.content-wrapper
div#musician-search-filter-results-header
a#btn-musician-search-builder.button-orange href="#" SEARCH
- span.musician-search-text Click search button to look for musicians with similar interests, skill levels, etc.
a#btn-musician-search-reset.button-grey href="#" RESET
div#musician-search-filter-description
div.clearall
@@ -22,51 +21,51 @@ script#template-musician-search-filter type="text/template"
.col-left
h2 search musicians
.col-right.builder-sort-order
- .text-label Sort Results By:
- select.easydropdown name="sort_order"
- option selected="selected" value="{sort_order}" {sort_order}
+ .text-label Sort Results By:
+ select.easydropdown name="sort_order"
+ option selected="selected" value="{sort_order}" {sort_order}
.clearall
#musician-search-filter-builder-middle.builder-section
.col-left
.field
- label for="search-filter-genres" Genres
- .search-filter-setup-genres.band-setup-genres
- #search-filter-genres
+ label for="search-filter-genres" Genres:
+ .search-filter-setup-genres.musician-setup-genres
+ table#search-filter-genres cellpadding="10" cellspacing="6" width="100%"
.field.builder-selector
- label Interests
+ label Interests:
select.easydropdown name="interests"
option selected="selected" value="{interests}" {interests}
.field.builder-selector
- label Studio Sessions Played
+ label Studio Sessions Played:
select.easydropdown name="studio_sessions"
option selected="selected" value="{studio_sessions}" {studio_sessions}
.col-right
.field
label for="search-filter-instruments"
- | Instruments & Skill Level
- .search-filter-setup-instruments.band-setup-genres.builder-instruments.session-instrumentlist
+ | Instruments & Skill Level:
+ .search-filter-setup-instruments.musician-setup-genres.builder-instruments
table#search-filter-instruments cellpadding="10" cellspacing="6" width="100%"
.col-left
.field.builder-selector
- label Status
+ label Status:
select.easydropdown name="skill_level"
option selected="selected" value="{skill_level}" {skill_label}
.field.builder-selector
- label Concert Gigs Played
+ label Concert Gigs Played:
select.easydropdown name="concert_gigs"
option selected="selected" value="{concert_gigs}" {concert_gigs}
.col-right
.field.builder-selector
- label for="search-filter-ages" Ages
- .search-filter-setup-ages.band-setup-genres.builder-ages
- #search-filter-ages
+ label for="search-filter-ages" Ages:
+ .search-filter-setup-ages.builder-ages
+ table#search-filter-ages cellpadding="10" cellspacing="6" width="100%"
.clearall
.clearall
@@ -85,25 +84,13 @@ script#template-search-filter-setup-instrument type="text/template"
option value="2" Intermediate
option value="3" Expert
-script#template-search-filter-setup-genres type="text/template"
- .genre-option
- | {% if(data.checked) { %}
- input value="{{data.id}}" checked="checked" type="checkbox"
- | {% } else { %}
- input value="{{data.id}}" type="checkbox"
- | {% } %}
- label
- | {{data.description}}
+script#template-search-filter-setup-genres type="text/template"
+ tr
+ td {description}
-script#template-search-filter-setup-ages type="text/template"
- .age-option
- | {% if(data.checked) { %}
- input value="{{data.id}}" checked="checked" type="checkbox"
- | {% } else { %}
- input value="{{data.id}}" type="checkbox"
- | {% } %}
- label
- | {{data.description}}
+script#template-search-filter-setup-ages type="text/template"
+ tr
+ td {description}
/! Session Row Template
script#template-search-musician-row type="text/template"
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index acc908a02..ca1c5c24b 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -314,21 +314,17 @@
// var findMusicianScreen = new JK.FindMusicianScreen(JK.app);
//findMusicianScreen.initialize(JK.TextMessageDialogInstance);
+ var findBandScreen = new JK.BandSearchFilter();
+ findBandScreen.init(JK.app);
+
var redeemSignUpScreen = new JK.RedeemSignUpScreen(JK.app);
redeemSignUpScreen.initialize();
var redeemCompleteScreen = new JK.RedeemCompleteScreen(JK.app);
redeemCompleteScreen.initialize();
- var findBandScreen = new JK.FindBandScreen(JK.app);
- findBandScreen.initialize();
-
- //var sessionScreen = new JK.SessionScreen(JK.app);
- //sessionScreen.initialize(localRecordingsDialog, recordingFinishedDialog, JK.FriendSelectorDialogInstance);
-
AppActions.appInit.trigger(JK.app)
-
var addNewGearDialog = new JK.AddNewGearDialog(JK.app);
addNewGearDialog.initialize();
diff --git a/web/app/views/spikes/band_search_filter.html.slim b/web/app/views/spikes/band_search_filter.html.slim
new file mode 100644
index 000000000..a98478205
--- /dev/null
+++ b/web/app/views/spikes/band_search_filter.html.slim
@@ -0,0 +1,15 @@
+= javascript_include_tag "profile_utils"
+= javascript_include_tag "member_search_filter"
+= stylesheet_link_tag "client/band"
+#bands-screen
+ = render "clients/band_search_filter"
+
+javascript:
+ var initialized = false;
+ $(document).on('JAMKAZAM_READY', function(e, data) {
+ setTimeout(function() {
+ window.band_search_filter = new JK.BandSearchFilter();
+ band_search_filter.init(data.app);
+ band_search_filter.afterShow();
+ }, 1)
+ });
diff --git a/web/app/views/spikes/musician_search_filter.html.slim b/web/app/views/spikes/musician_search_filter.html.slim
index 777cf2636..d6ae1f1da 100644
--- a/web/app/views/spikes/musician_search_filter.html.slim
+++ b/web/app/views/spikes/musician_search_filter.html.slim
@@ -1,15 +1,15 @@
= javascript_include_tag "profile_utils"
-= javascript_include_tag "musician_search_filter"
+= javascript_include_tag "member_search_filter"
= stylesheet_link_tag "client/musician"
-#musician_search_spike
-= render "clients/musician_search_filter"
+#musicians-screen
+ = render "clients/musician_search_filter"
javascript:
var initialized = false;
$(document).on('JAMKAZAM_READY', function(e, data) {
setTimeout(function() {
window.musician_search_filter = new JK.MusicianSearchFilter();
- musician_search_filter.init();
+ musician_search_filter.init(data.app);
musician_search_filter.afterShow();
}, 1)
});
diff --git a/web/config/routes.rb b/web/config/routes.rb
index 53f3acaee..77c641200 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -118,6 +118,7 @@ SampleApp::Application.routes.draw do
match '/site_validate', to: 'spikes#site_validate'
match '/recording_source', to: 'spikes#recording_source'
match '/musician_search_filter', to: 'spikes#musician_search_filter'
+ match '/band_search_filter', to: 'spikes#band_search_filter'
# junk pages
match '/help', to: 'static_pages#help'
@@ -485,6 +486,7 @@ SampleApp::Application.routes.draw do
# search
match '/search' => 'api_search#index', :via => :get
match '/search/musicians' => 'api_search#musicians', :via => [:get, :post]
+ match '/search/bands' => 'api_search#bands', :via => [:get, :post]
# join requests
match '/join_requests/:id' => 'api_join_requests#show', :via => :get, :as => 'api_join_request_detail'
diff --git a/web/lib/max_mind_manager.rb b/web/lib/max_mind_manager.rb
index fceef6353..db630195c 100644
--- a/web/lib/max_mind_manager.rb
+++ b/web/lib/max_mind_manager.rb
@@ -5,24 +5,11 @@ class MaxMindManager < BaseManager
end
def self.countries
- Country.get_all.map do |c|
- country = Carmen::Country.coded(c.countrycode)
- {
- countrycode: c.countrycode,
- countryname: country.name
- }
- end
+ Country.get_all.map { |c| {countrycode: c.countrycode, countryname: c.countryname} }
end
def self.regions(country)
- Region.get_all(country).map do |r|
- country = Carmen::Country.coded(r.countrycode)
- region = country.subregions.coded(r.region)
- {
- region: r.region,
- name: region.name
- }
- end
+ Region.get_all(country).map { |r| { region: r.region, name: r.regionname } }
end
def self.cities(country, region)
diff --git a/web/lib/tasks/jam_tracks.rake b/web/lib/tasks/jam_tracks.rake
index 4d28e89a4..18942fe02 100644
--- a/web/lib/tasks/jam_tracks.rake
+++ b/web/lib/tasks/jam_tracks.rake
@@ -4,15 +4,49 @@ namespace :jam_tracks do
JamTrackImporter.dry_run
end
+ task tency_dry_run: :environment do |task, args|
+ JamTrackImporter.storage_format = 'Tency'
+ JamTrackImporter.dry_run
+ end
+
+ task tency_create_masters: :environment do |task, args|
+ JamTrackImporter.storage_format = 'Tency'
+ JamTrackImporter.create_masters
+ end
+
+
+ task tency_create_master: :environment do |task, args|
+ JamTrackImporter.storage_format = 'Tency'
+
+ path = ENV['TRACK_PATH']
+
+ if !path
+ puts "TRACK_PATH must be set to something like audio/AC DC/Back in Black or mapped/50 Cent - In Da Club - 12401"
+ exit(1)
+ end
+
+ JamTrackImporter.create_master(path)
+ end
+
+
+ task tency_delta: :environment do |task, args|
+ JamTrackImporter.storage_format = 'Tency'
+ JamTrackImporter.tency_delta
+ end
+
task sync: :environment do |task, args|
path = ENV['TRACK_PATH']
if !path
- puts "TRACK_PATH must be set to something like AD DC/Back in Black"
+ puts "TRACK_PATH must be set to something like audio/AC DC/Back in Black or mapped/50 Cent - In Da Club - 12401"
exit(1)
end
- JamTrackImporter.synchronize_from_meta("audio/#{path}/meta.yml", skip_audio_upload:false)
+ if path.start_with?('mapped')
+ JamTrackImporter.storage_format = 'Tency'
+ end
+
+ JamTrackImporter.synchronize_from_meta("#{path}/meta.yml", skip_audio_upload:false)
end
task resync_audio: :environment do |task, args|
@@ -26,11 +60,26 @@ namespace :jam_tracks do
JamTrackImporter.synchronize_from_meta("audio/#{path}/meta.yml", resync_audio:true, skip_audio_upload:false)
end
+ task tency_genre_dump: :environment do |task, args|
+ JamTrackImporter.storage_format = 'Tency'
+ JamTrackImporter.genre_dump
+ end
+
+ task sync_tency: :environment do |task, args|
+ JamTrackImporter.storage_format = 'Tency'
+ JamTrackImporter.synchronize_all(skip_audio_upload:false)
+ end
+
+ task onboarding_exceptions: :environment do |task, args|
+ JamTrackImporter.onboarding_exceptions
+ end
+
task sync_all: :environment do |task, args|
JamTrackImporter.synchronize_all(skip_audio_upload:false)
end
task sync_all_dev: :environment do |task, args|
+ JamTrackImporter.storage_format = 'default'
JamTrackImporter.synchronize_all(skip_audio_upload:true)
end
@@ -91,4 +140,10 @@ namespace :jam_tracks do
task download_masters: :environment do |task, arg|
JamTrackImporter.download_masters
end
+
+
+ task tency: :environment do |task, arg|
+ mapper = TencyStemMapping.new
+ mapper.correlate
+ end
end
diff --git a/web/lib/tasks/sample_data.rake b/web/lib/tasks/sample_data.rake
index 8ba62eb5a..58d25782b 100644
--- a/web/lib/tasks/sample_data.rake
+++ b/web/lib/tasks/sample_data.rake
@@ -139,7 +139,7 @@ end
def make_band_members
Band.find_each do |bb|
User.order('RANDOM()').limit(4).each do |uu|
- BandMusician.create!({:user_id => uu.id, :band_id => bb.id})
+ BandMusician.create({:user_id => uu.id, :band_id => bb.id})
end
end
end
diff --git a/web/spec/controllers/api_jam_tracks_controller_spec.rb b/web/spec/controllers/api_jam_tracks_controller_spec.rb
index 74bc1da94..25f971ec9 100644
--- a/web/spec/controllers/api_jam_tracks_controller_spec.rb
+++ b/web/spec/controllers/api_jam_tracks_controller_spec.rb
@@ -128,6 +128,7 @@ describe ApiJamTracksController do
# of this process is checked in other tests:
@ogg_path = File.join('spec', 'files', 'on.ogg')
@jam_track = FactoryGirl.create(:jam_track) #jam_track_track.jam_track
+ @jam_track.reload
jam_track_track = @jam_track.jam_track_tracks.first
# 48 kHz:
diff --git a/web/spec/factories.rb b/web/spec/factories.rb
index 8260c2d29..58feba086 100644
--- a/web/spec/factories.rb
+++ b/web/spec/factories.rb
@@ -743,7 +743,7 @@ FactoryGirl.define do
make_track true
end
- genre JamRuby::Genre.first
+ genres [JamRuby::Genre.first]
association :licensor, factory: :jam_track_licensor
after(:create) do |jam_track, evaluator|
diff --git a/web/spec/features/jamtrack_shopping_spec.rb b/web/spec/features/jamtrack_shopping_spec.rb
index 23da6a3f4..a3171a282 100644
--- a/web/spec/features/jamtrack_shopping_spec.rb
+++ b/web/spec/features/jamtrack_shopping_spec.rb
@@ -5,8 +5,8 @@ describe "JamTrack Shopping", :js => true, :type => :feature, :capybara_feature
let(:user) { FactoryGirl.create(:user, has_redeemable_jamtrack: false) }
let(:jt_us) { FactoryGirl.create(:jam_track, :name=>'jt_us', sales_region: 'Worldwide', make_track: true, original_artist: "foobar") }
let(:jt_ww) { FactoryGirl.create(:jam_track, :name=>'jt_ww', sales_region: 'Worldwide', make_track: true, original_artist: "barfoo") }
- let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genre: JamRuby::Genre.find('rock'), make_track: true, original_artist: "badfood") }
- let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genre: JamRuby::Genre.find('blues'), make_track: true, original_artist: "foodbart") }
+ let(:jt_rock) { FactoryGirl.create(:jam_track, :name=>'jt_rock', genres: [JamRuby::Genre.find('rock')], make_track: true, original_artist: "badfood") }
+ let(:jt_blues) { FactoryGirl.create(:jam_track, :name=>'jt_blues', genres: [JamRuby::Genre.find('blues')], make_track: true, original_artist: "foodbart") }
before(:all) do
Capybara.javascript_driver = :poltergeist