\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.text.erb
deleted file mode 100644
index 40ec73f65..000000000
--- a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder.text.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-<%= @body %>
-
-<%= @session_name %>
-<%= @session_date %>
-
-See session details at <%= @session_url %>.
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb
new file mode 100644
index 000000000..b72d3c133
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.html.erb
@@ -0,0 +1,18 @@
+<% provide(:title, 'JamKazam Session Reminder') %>
+
+
+
+Hi <%= @user.first_name %>,
+
+
+
+ This is a reminder that your JamKazam session
+ <%= @session_name %>
+ is scheduled for tomorrow. We hope you have fun!
+
+
+
+Best Regards,
+
+Team JamKazam
+
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb
new file mode 100644
index 000000000..c3f0576bf
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_day.text.erb
@@ -0,0 +1,8 @@
+Hi <%= @user.first_name %>,
+
+This is a reminder that your JamKazam session <%=@session_name%> is scheduled for tomorrow. We hope you have fun!
+
+Best Regards,
+Team JamKazam
+
+See session details at <%= @session_url %>.
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb
new file mode 100644
index 000000000..4fbc59ace
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.html.erb
@@ -0,0 +1,17 @@
+<% provide(:title, 'Your JamKazam session starts in 1 hour!') %>
+
+
+Hi <%= @user.first_name %>,
+
+
+
+ This is a reminder that your JamKazam session
+ <%= @session_name %>
+ starts in 1 hour. We hope you have fun!
+
+
+
+Best Regards,
+
+Team JamKazam
+
diff --git a/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb
new file mode 100644
index 000000000..70726a9e6
--- /dev/null
+++ b/ruby/lib/jam_ruby/app/views/jam_ruby/user_mailer/scheduled_session_reminder_upcoming.text.erb
@@ -0,0 +1,10 @@
+Hi <%= @user.first_name %>,
+
+This is a reminder that your JamKazam session
+<%=@session_name%>
+starts in 1 hour. We hope you have fun!
+
+Best Regards,
+Team JamKazam
+
+See session details at <%= @session_url %>.
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/calendar_manager.rb b/ruby/lib/jam_ruby/calendar_manager.rb
new file mode 100644
index 000000000..523e683d9
--- /dev/null
+++ b/ruby/lib/jam_ruby/calendar_manager.rb
@@ -0,0 +1,106 @@
+module JamRuby
+ class CalendarManager < BaseManager
+ DATE_FORMAT="%Y%m%dT%H%M%SZ"
+ def initialize(options={})
+ super(options)
+ @log = Logging.logger[self]
+ end
+
+ def cancel_ics_event(music_session, user)
+ Calendar.where(
+ user_id: user.id,
+ target_uid: music_session.id,
+ name: music_session.description)
+ .first_or_create(
+ description: music_session.description,
+ start_at: music_session.scheduled_start,
+ end_at: music_session.scheduled_start+music_session.safe_scheduled_duration,
+ trigger_delete: true)
+
+ end
+
+ # Remove all "delete" event calendar records older than 4 weeks:
+ def cleanup()
+ Calendar.where("trigger_delete=TRUE AND created_at < ?", 4.weeks.ago)
+ .destroy_all()
+ end
+
+ # @return event (as ICS string) for a given music session
+ def ics_event_from_music_session(music_session, delete=false)
+ # Determine properties of calendar event and create:
+ uid = "#{music_session.id}@JamKazam"
+ text = "JamKazam Session #{music_session.description}"
+ rrule = nil
+ start_at = music_session.scheduled_start
+ stop_at = music_session.scheduled_start+music_session.safe_scheduled_duration
+ if !delete && music_session.recurring_mode==MusicSession::RECURRING_WEEKLY
+ rrule = "FREQ=WEEKLY;INTERVAL=1"
+ end
+ create_ics_event(uid, text, text, start_at, stop_at, delete, rrule)
+ end
+
+ # @return event (as ICS string) for a given music session
+ def ics_event_from_calendar(calendar)
+ # Determine properties of calendar event and create:
+ rrule = nil
+ if !calendar.trigger_delete && calendar.recurring_mode==MusicSession::RECURRING_WEEKLY
+ rrule = "FREQ=WEEKLY;INTERVAL=1"
+ end
+
+ create_ics_event(
+ calendar.target_uid,
+ "JamKazam Session #{calendar.name}",
+ calendar.description,
+ calendar.start_at,
+ calendar.end_at,
+ calendar.trigger_delete,
+ rrule
+ )
+ end
+
+ # @return calendar (as ICS string) for specified user
+ # Includes all RSVPed sessions, as well as any calendar
+ # entries for the given user:
+ def create_ics_feed(user)
+ ics_events = ""
+ MusicSession.scheduled_rsvp(user, true).each do |music_session|
+ ics_events << "\r\n" if(ics_events.length != 0)
+ ics_events << ics_event_from_music_session(music_session)
+ end
+
+ user.calendars.each do |user|
+ ics_events << "\r\n" if(ics_events.length != 0)
+ ics_events << ics_event_from_calendar(user)
+ end
+
+ create_ics_cal(ics_events)
+ end
+
+ # @return event (as ICS string) for given arguments
+ def create_ics_event(uuid, name, description, start_at, end_at, delete=false, rrule=nil, sequence=nil)
+ uuid ||= UUID.timestamp_create
+ event = "BEGIN:VEVENT\r\n"
+ event << "UID:#{uuid}\r\n"
+ event << "DTSTAMP:#{Time.now.utc().strftime(DATE_FORMAT)}\r\n"
+ event << "DTSTART:#{start_at.utc().strftime(DATE_FORMAT)}\r\n"
+ event << "DTEND:#{end_at.utc().strftime(DATE_FORMAT)}\r\n"
+ event << "SUMMARY:#{name}\r\n"
+ event << "DESCRIPTION:#{description}\r\n"
+ if delete
+ event << "METHOD:CANCEL\r\n"
+ event << "STATUS:CANCELLED\r\n"
+ end
+ if rrule
+ event << "RRULE:#{rrule}\r\n"
+ end
+ event << "SEQUENCE:#{sequence}\r\n" if sequence
+ event << "END:VEVENT"
+ end
+
+ # @return calendar (as ICS string) for specified events
+ def create_ics_cal(ics_events)
+ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:JamKazam\r\n#{ics_events}\r\nEND:VCALENDAR"
+ end
+
+ end # class
+end # module
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/constants/notification_types.rb b/ruby/lib/jam_ruby/constants/notification_types.rb
index 7460a2fbb..e05c8e00e 100644
--- a/ruby/lib/jam_ruby/constants/notification_types.rb
+++ b/ruby/lib/jam_ruby/constants/notification_types.rb
@@ -26,7 +26,9 @@ module NotificationTypes
SCHEDULED_SESSION_RSVP_CANCELLED_ORG = "SCHEDULED_SESSION_RSVP_CANCELLED_ORG"
SCHEDULED_SESSION_CANCELLED = "SCHEDULED_SESSION_CANCELLED"
SCHEDULED_SESSION_RESCHEDULED = "SCHEDULED_SESSION_RESCHEDULED"
- SCHEDULED_SESSION_REMINDER = "SCHEDULED_SESSION_REMINDER"
+ SCHEDULED_SESSION_REMINDER_DAY = "SCHEDULED_SESSION_REMINDER_DAY"
+ SCHEDULED_SESSION_REMINDER_UPCOMING = "SCHEDULED_SESSION_REMINDER_UPCOMING"
+ SCHEDULED_SESSION_REMINDER_IMMINENT = "SCHEDULED_SESSION_REMINDER_IMMINENT"
SCHEDULED_SESSION_COMMENT = "SCHEDULED_SESSION_COMMENT"
# recording notifications
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..f929a8545 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,6 +278,83 @@ 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
+
def synchronize_metadata(jam_track, metadata, metalocation, original_artist, name, options)
metadata ||= {}
@@ -104,14 +368,24 @@ module JamRuby
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")
@@ -167,12 +441,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 +465,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 +494,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 +552,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 +624,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 +854,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 +887,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 +942,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 +1007,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 +1055,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 +1087,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 +1206,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 +1231,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 +1242,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 +1262,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}'")
@@ -906,23 +1667,35 @@ 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'
+ count+=1
+ end
+
+ if count > 500
+ break
end
end
+
@@log.info("SUMMARY")
@@log.info("-------")
importers.each do |importer|
@@ -956,12 +1729,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,7 +1849,7 @@ 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.transaction do
#begin
@@ -998,6 +1872,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/lib/stats.rb b/ruby/lib/jam_ruby/lib/stats.rb
index c51a14ae1..697965526 100644
--- a/ruby/lib/jam_ruby/lib/stats.rb
+++ b/ruby/lib/jam_ruby/lib/stats.rb
@@ -40,7 +40,7 @@ module JamRuby
class Stats
class << self
- attr_accessor :client, :host
+ attr_accessor :client, :host, :ignore
@@log = Logging.logger[JamRuby::Stats]
end
@@ -52,6 +52,7 @@ module JamRuby
end
def self.init(options)
+
influxdb_database = options[:influxdb_database]
influxdb_username = options[:influxdb_username]
influxdb_password = options[:influxdb_password]
@@ -71,12 +72,15 @@ module JamRuby
retry: -1
@host = `hostname`.strip
else
+ self.ignore = true
@@log.debug("stats client not initiated")
end
end
def self.write(name, data)
+ return if self.ignore # doing any writes in a test environment cause annoying puts to occur
+
if @client && data && data.length > 0
data['host'] = @host
data['time'] = Time.now.to_i
diff --git a/ruby/lib/jam_ruby/models/band.rb b/ruby/lib/jam_ruby/models/band.rb
index 47b5cfe97..2ba858c6e 100644
--- a/ruby/lib/jam_ruby/models/band.rb
+++ b/ruby/lib/jam_ruby/models/band.rb
@@ -3,10 +3,10 @@ module JamRuby
include HtmlSanitize
html_sanitize strict: [:biography, :website, :name]
- attr_accessible :name, :website, :biography, :city, :state,
+ attr_accessible :name, :website, :biography, :city, :state,
:country, :original_fpfile_photo, :cropped_fpfile_photo, :cropped_large_fpfile_photo,
:cropped_s3_path_photo, :cropped_large_s3_path_photo, :crop_selection_photo, :photo_url, :large_photo_url,
- :band_type, :band_status, :concert_count, :add_new_members, :play_commitment, :touring_option, :paid_gigs,
+ :band_type, :band_status, :concert_count, :add_new_members, :play_commitment, :touring_option, :paid_gigs,
:free_gigs, :hourly_rate, :gig_minimum
attr_accessor :updating_photo, :skip_location_validation, :skip_genre_validation
@@ -23,6 +23,8 @@ module JamRuby
validate :validate_photo_info
validate :require_at_least_one_genre, :unless => :skip_genre_validation
validate :limit_max_genres
+ validates_numericality_of :hourly_rate, greater_than:0, less_than:100000, :if => :paid_gigs
+ validates_numericality_of :gig_minimum, greater_than:0, less_than:200000, :if => :paid_gigs
before_save :check_lat_lng
before_save :check_website_url
@@ -47,10 +49,10 @@ module JamRuby
has_many :recordings, :class_name => "JamRuby::Recording", :foreign_key => "band_id", dependent: :destroy
# self.id = likable_id in likes table
- has_many :likers, :as => :likable, :class_name => "JamRuby::Like", :dependent => :destroy, dependent: :destroy
+ has_many :likers, :as => :likable, :class_name => "JamRuby::Like", :dependent => :destroy
# self.id = followable_id in follows table
- has_many :followers, :as => :followable, :class_name => "JamRuby::Follow", :dependent => :destroy, dependent: :destroy
+ has_many :followers, :as => :followable, :class_name => "JamRuby::Follow", :dependent => :destroy
# invitations
has_many :invitations, :inverse_of => :band, :class_name => "JamRuby::BandInvitation", :foreign_key => "band_id", dependent: :destroy
@@ -180,7 +182,7 @@ module JamRuby
band = id.blank? ? Band.new : Band.find(id)
# ensure user updating Band details is a Band member
- unless band.new_record? || band.users.exists?(user)
+ unless band.new_record? || band.users.exists?(user)
raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
end
@@ -193,15 +195,28 @@ module JamRuby
band.photo_url = params[:photo_url] if params.has_key?(:photo_url)
band.logo_url = params[:logo_url] if params.has_key?(:logo_url)
- if params.has_key?(:genres) && params[:genres]
+ band.paid_gigs = params[:paid_gigs] if params.has_key?(:paid_gigs)
+ band.free_gigs = params[:free_gigs] if params.has_key?(:free_gigs)
+ band.hourly_rate = (params.has_key?(:hourly_rate) && params[:hourly_rate].to_i > 0) ? params[:hourly_rate] : nil
+ band.gig_minimum = (params.has_key?(:gig_minimum) && params[:hourly_rate].to_i > 0) ? params[:gig_minimum] : nil
+ band.add_new_members = params[:add_new_members] if params.has_key?(:add_new_members)
+ band.touring_option = params[:touring_option] if params.has_key?(:touring_option)
+ band.band_type = params[:band_type] if params.has_key?(:band_type)
+ band.band_status = params[:band_status] if params.has_key?(:band_status)
+ band.concert_count = params[:concert_count] if params.has_key?(:concert_count)
+ band.play_commitment = params[:play_commitment] if params.has_key?(:play_commitment)
+
+ if params[:validate_genres] || params[:genres].present?
# loop through each genre in the array and save to the db
genres = []
- params[:genres].each { |genre_id| genres << Genre.find(genre_id) }
+ params[:genres].each { |genre_id| genres << Genre.find(genre_id) } if params[:genres].present?
band.genres = genres
+ band.skip_genre_validation = false
+ else
+ params[:validate_genres]
+ band.skip_genre_validation = true
end
- band.skip_genre_validation = true unless params[:validate_genres]
-
unless band.new_record?
OnlinePresence.delete_all(["player_id = ?", band.id])
PerformanceSample.delete_all(["player_id = ?", band.id])
@@ -210,7 +225,7 @@ module JamRuby
online_presences = params[:online_presences]
if online_presences.present?
online_presences.each do |op|
- new_presence = OnlinePresence.create(band, op, false)
+ new_presence = OnlinePresence.create(band, op, false)
band.online_presences << new_presence
end
end
@@ -218,15 +233,15 @@ module JamRuby
performance_samples = params[:performance_samples]
if performance_samples.present?
performance_samples.each do |ps|
- band.performance_samples << PerformanceSample.create(band, ps, false)
+ band.performance_samples << PerformanceSample.create(band, ps, false)
end
end
-
+
band
end
# helper method for creating / updating a Band
- def self.save(user, params)
+ def self.save(user, params)
band = build_band(user, params)
if band.save
@@ -284,7 +299,7 @@ module JamRuby
def check_lat_lng
if (city_changed? || state_changed? || country_changed?)
- update_lat_lng
+ update_lat_lng
end
true
end
diff --git a/ruby/lib/jam_ruby/models/calendar.rb b/ruby/lib/jam_ruby/models/calendar.rb
new file mode 100644
index 000000000..4f244b587
--- /dev/null
+++ b/ruby/lib/jam_ruby/models/calendar.rb
@@ -0,0 +1,14 @@
+module JamRuby
+ class Calendar < ActiveRecord::Base
+ include HtmlSanitize
+ html_sanitize strict: [:name, :description]
+ attr_accessible :name, :description, :target_uid, :trigger_delete, :start_at, :end_at
+
+ @@log = Logging.logger[Calendar]
+
+ self.table_name = "calendars"
+ self.primary_key = 'id'
+
+ belongs_to :user, :class_name => 'JamRuby::User', :foreign_key => :user_id, :inverse_of => :calendars
+ end
+end
diff --git a/ruby/lib/jam_ruby/models/email_batch_scheduled_sessions.rb b/ruby/lib/jam_ruby/models/email_batch_scheduled_sessions.rb
index d388bce9e..68dfb9fb0 100644
--- a/ruby/lib/jam_ruby/models/email_batch_scheduled_sessions.rb
+++ b/ruby/lib/jam_ruby/models/email_batch_scheduled_sessions.rb
@@ -200,7 +200,7 @@ SELECT
tmp_candidate_sessions.creator_score_idx AS creator_score_idx
INTO TEMP TABLE tmp_candidate_recipients
FROM users
-INNER JOIN musicians_instruments AS mi ON mi.user_id = users.id
+INNER JOIN musicians_instruments AS mi ON mi.player_id = users.id
INNER JOIN tmp_candidate_sessions ON tmp_candidate_sessions.is_unstructured_rsvp = TRUE OR
(tmp_candidate_sessions.open_rsvps = TRUE AND tmp_candidate_sessions.instrument_id = mi.instrument_id) OR
tmp_candidate_sessions.invited_user_id = users.id
diff --git a/ruby/lib/jam_ruby/models/genre.rb b/ruby/lib/jam_ruby/models/genre.rb
index 1b0cd9ada..91d80f755 100644
--- a/ruby/lib/jam_ruby/models/genre.rb
+++ b/ruby/lib/jam_ruby/models/genre.rb
@@ -16,7 +16,8 @@ module JamRuby
has_and_belongs_to_many :recordings, :class_name => "JamRuby::Recording", :join_table => "recordings_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..c99246876 100644
--- a/ruby/lib/jam_ruby/models/jam_track_track.rb
+++ b/ruby/lib/jam_ruby/models/jam_track_track.rb
@@ -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/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb
index 96b9b7831..1f56efc08 100644
--- a/ruby/lib/jam_ruby/models/music_session.rb
+++ b/ruby/lib/jam_ruby/models/music_session.rb
@@ -44,10 +44,10 @@ module JamRuby
has_one :share_token, :class_name => "JamRuby::ShareToken", :inverse_of => :shareable, :foreign_key => 'shareable_id'
has_one :feed, :class_name => "JamRuby::Feed", :inverse_of => :music_session, :foreign_key => 'music_session_id', :dependent => :destroy
belongs_to :genre, :class_name => "JamRuby::Genre", :inverse_of => :music_sessions, :foreign_key => 'genre_id'
- has_many :join_requests, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::JoinRequest", :foreign_key => "music_session_id"
- has_many :invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::Invitation", :foreign_key => "music_session_id"
+ has_many :join_requests, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::JoinRequest"
+ has_many :invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::Invitation"
has_many :invited_musicians, :through => :invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
- has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation", :foreign_key => "music_session_id"
+ has_many :fan_invitations, :foreign_key => "music_session_id", :inverse_of => :music_session, :class_name => "JamRuby::FanInvitation"
has_many :invited_fans, :through => :fan_invitations, :class_name => "JamRuby::User", :foreign_key => "receiver_id", :source => :receiver
has_many :rsvp_slots, :class_name => "JamRuby::RsvpSlot", :foreign_key => "music_session_id", :dependent => :destroy
has_many :music_notations, :class_name => "JamRuby::MusicNotation", :foreign_key => "music_session_id"
@@ -880,6 +880,21 @@ SQL
end
result
end
+
+ def safe_scheduled_duration
+ duration = scheduled_duration
+ # you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string
+ if scheduled_duration.class == String
+ begin
+ bits = scheduled_duration.split(':')
+ duration = bits[0].to_i.hours + bits[1].to_i.minutes + bits[2].to_i.seconds
+ rescue Exception => e
+ duration = 1.hours
+ @@log.error("unable to parse duration #{scheduled_duration}")
+ end
+ end
+ duration
+ end
# should create a timestamp like:
#
# with_timezone = TRUE
@@ -910,17 +925,7 @@ SQL
end
end
- duration = scheduled_duration
- # you can put seconds into the scheduled_duration field, but once stored, it comes back out as a string
- if scheduled_duration.class == String
- begin
- bits = scheduled_duration.split(':')
- duration = bits[0].to_i.hours + bits[1].to_i.minutes + bits[2].to_i.seconds
- rescue Exception => e
- duration = 1.hours
- @@log.error("unable to parse duration #{scheduled_duration}")
- end
- end
+ duration = safe_scheduled_duration
end_time = start_time + duration
if with_timezone
"#{start_time.strftime("%A, %B %e")}, #{start_time.strftime("%l:%M").strip}-#{end_time.strftime("%l:%M %p").strip} #{timezone_display}"
diff --git a/ruby/lib/jam_ruby/models/music_session_user_history.rb b/ruby/lib/jam_ruby/models/music_session_user_history.rb
index bca0ecccc..86d132025 100644
--- a/ruby/lib/jam_ruby/models/music_session_user_history.rb
+++ b/ruby/lib/jam_ruby/models/music_session_user_history.rb
@@ -10,14 +10,8 @@ module JamRuby
attr_accessible :max_concurrent_connections, :session_removed_at, :rating
validates_inclusion_of :rating, :in => -1..1, :allow_nil => true
- belongs_to(:user,
- :class_name => "JamRuby::User",
- :foreign_key => "user_id",
- :inverse_of => :music_session_user_histories)
-
- belongs_to(:music_session,
- :class_name => "MusicSession",
- :foreign_key => "music_session_id")
+ belongs_to :user, :class_name => "JamRuby::User", :foreign_key => "user_id", :inverse_of => :music_session_user_histories
+ belongs_to :music_session, :class_name => "MusicSession", :foreign_key => "music_session_id"
def self.latest_history(client_id)
self.where(:client_id => client_id)
diff --git a/ruby/lib/jam_ruby/models/musician_search.rb b/ruby/lib/jam_ruby/models/musician_search.rb
index a5a2cd270..f11e36e20 100644
--- a/ruby/lib/jam_ruby/models/musician_search.rb
+++ b/ruby/lib/jam_ruby/models/musician_search.rb
@@ -246,47 +246,54 @@ module JamRuby
return 'Click search button to look for musicians with similar interests, skill levels, etc.'
end
jj = self.json
- str = 'Current Search: '
- str += "Sort = #{SORT_ORDERS[json_value(MusicianSearch::KEY_SORT_ORDER)]}"
+ str = ''
+ if 0 < (val = jj[KEY_INSTRUMENTS]).length
+ str += ", Instruments = "
+ instr_ids = val.collect { |stored_instrument| stored_instrument['id'] }
+ instrs = Instrument.where(["id IN (?)", instr_ids]).order(:description)
+ instrs.each_with_index do |ii, idx|
+ proficiency = val.detect { |stored_instrument| stored_instrument['id'] == ii.id }['level']
+ str += "#{ii.description} / #{INSTRUMENT_PROFICIENCY[proficiency.to_i]}"
+ str += ', ' unless idx==(instrs.length-1)
+ end
+ end
+
if (val = jj[KEY_INTERESTS]) != INTEREST_VALS[0]
- str += "; Interest = #{INTERESTS[val]}"
+ str += ", Interest = #{INTERESTS[val]}"
end
if (val = jj[KEY_SKILL].to_i) != SKILL_VALS[0]
- str += "; Skill = #{SKILL_LEVELS[val]}"
+ str += ", Skill = #{SKILL_LEVELS[val]}"
end
if (val = jj[KEY_STUDIOS].to_i) != STUDIO_COUNTS[0]
- str += "; Studio Sessions = #{STUDIOS_LABELS[val]}"
+ str += ", Studio Sessions = #{STUDIOS_LABELS[val]}"
end
if (val = jj[KEY_GIGS].to_i) != GIG_COUNTS[0]
- str += "; Concert Gigs = #{GIG_LABELS[val]}"
+ str += ", Concert Gigs = #{GIG_LABELS[val]}"
end
val = jj[KEY_AGES].map(&:to_i)
val.sort!
if !val.blank?
- str += "; Ages = "
+ str += ", Ages = "
val.each_with_index do |vv, idx|
str += "#{AGES[vv]}"
str += ', ' unless idx==(val.length-1)
end
end
if 0 < (val = jj[KEY_GENRES]).length
- str += "; Genres = "
+ str += ", Genres = "
genres = Genre.where(["id IN (?)", val]).order('description').pluck(:description)
genres.each_with_index do |gg, idx|
str += "#{gg}"
str += ', ' unless idx==(genres.length-1)
end
end
- if 0 < (val = jj[KEY_INSTRUMENTS]).length
- str += "; 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']
- str += "#{ii.description} (#{INSTRUMENT_PROFICIENCY[proficiency.to_i]})"
- str += ', ' unless idx==(instrs.length-1)
- end
+ str += ", Sort = #{SORT_ORDERS[json_value(MusicianSearch::KEY_SORT_ORDER)]}"
+
+ if str.start_with?(', ')
+ # trim off any leading ,
+ str = str[2..-1]
end
+ str = 'Current Search: ' + str
str
end
diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb
index db4018ab8..5b008a55f 100644
--- a/ruby/lib/jam_ruby/models/notification.rb
+++ b/ruby/lib/jam_ruby/models/notification.rb
@@ -73,6 +73,10 @@ module JamRuby
@@message_factory = MessageFactory.new
################### HELPERS ###################
+ def notified?(music_session, notification_type)
+ Notification.where("session_id=? AND description=?", music_session, notification_type).count != 0
+ end
+
def retrieve_friends(connection, user_id)
friend_ids = []
connection.exec("SELECT f.friend_id as friend_id FROM friendships f WHERE f.user_id = $1", [user_id]) do |friend_results|
@@ -203,9 +207,15 @@ module JamRuby
when NotificationTypes::SCHEDULED_SESSION_RESCHEDULED
return "The following session has been rescheduled."
- when NotificationTypes::SCHEDULED_SESSION_REMINDER
+ when NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY
return "A session to which you have RSVPd will begin in one hour, so get ready to play!"
+ when NotificationTypes::SCHEDULED_SESSION_REMINDER_UPCOMING
+ return "A session to which you have RSVPd will begin in one hour, so get ready to play!"
+
+ when NotificationTypes::SCHEDULED_SESSION_REMINDER_IMMINENT
+ return "A session to which you have RSVPd is scheduled to start in 5 minutes!"
+
when NotificationTypes::SCHEDULED_SESSION_COMMENT
return "New message about session."
@@ -515,7 +525,7 @@ module JamRuby
end
def send_session_join(active_music_session, connection, user)
-
+
notification_msg = format_msg(NotificationTypes::SESSION_JOIN, {:user => user})
msg = @@message_factory.session_join(
@@ -553,8 +563,8 @@ module JamRuby
end
def send_musician_session_join(music_session, user)
-
- if music_session.musician_access || music_session.fan_access
+
+ if music_session.musician_access || music_session.fan_access
friends = Friendship.where(:friend_id => user.id)
user_followers = user.followers
@@ -804,7 +814,7 @@ module JamRuby
def send_scheduled_session_cancelled(music_session)
return if music_session.nil?
-
+
rsvp_requests = RsvpRequest.index(music_session)
target_users = rsvp_requests.where(:canceled => false).map { |r| r.user }
@@ -890,33 +900,52 @@ module JamRuby
end
end
- def send_scheduled_session_reminder(music_session)
+ # Send session reminders to sessions that
+ # start in less than 24 hours, and haven't been
+ # notified for a particular interval yet:
+ def send_session_reminders
+ MusicSession.where("scheduled_start > NOW() AND scheduled_start <= (NOW()+INTERVAL '1 DAYS')").each do |candidate_session|
+ tm = candidate_session.scheduled_start
+ if (tm>(12.hours.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY))
+ # Send 24 hour reminders:
+ send_session_reminder_day(candidate_session)
+ elsif (tm<=(65.minutes.from_now) && tm>(15.minutes.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_UPCOMING))
+ # Send 1 hour reminders:
+ send_session_reminder_upcoming(candidate_session)
+ elsif (tm<=(10.minutes.from_now) && !notified?(candidate_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_IMMINENT))
+ # Send 5 minute reminders:
+ send_session_reminder_imminent(candidate_session)
+ end
+ end
+ end
- return if music_session.nil?
+ def send_session_reminder_day(music_session)
+ send_session_reminder(music_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_DAY) do |music_session, target_user, notification|
+ begin
+ UserMailer.scheduled_session_reminder_day(target_user, music_session).deliver
+ rescue => e
+ @@log.error("Unable to send SCHEDULED_SESSION_REMINDER_DAY email to user #{target_user.email} #{e}")
+ end
+ end
+ end
- rsvp_requests = RsvpRequest.index(music_session)
- target_users = rsvp_requests.where(:canceled => false).map { |r| r.user }
-
- # remove the creator from the array
- target_users = target_users.uniq - [music_session.creator]
-
- target_users.each do |target_user|
- source_user = music_session.creator
-
- notification = Notification.new
- notification.description = NotificationTypes::SCHEDULED_SESSION_REMINDER
- notification.source_user_id = source_user.id
- notification.target_user_id = target_user.id
- notification.session_id = music_session.id
- notification.save
-
- notification_msg = format_msg(notification.description, {:session => music_session})
+ def send_session_reminder_upcoming(music_session)
+ send_session_reminder(music_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_UPCOMING) do |music_session, target_user, notification|
+ begin
+ UserMailer.scheduled_session_reminder_upcoming(target_user, music_session).deliver
+ rescue => e
+ @@log.error("Unable to send SCHEDULED_SESSION_REMINDER_UPCOMING email to user #{target_user.email} #{e}")
+ end
+ end
+ end
+ def send_session_reminder_imminent(music_session)
+ send_session_reminder(music_session, NotificationTypes::SCHEDULED_SESSION_REMINDER_IMMINENT) do |music_session, target_user, notification|
if target_user.online
msg = @@message_factory.scheduled_session_reminder(
target_user.id,
music_session.id,
- notification_msg,
+ format_msg(notification.description, {:session => music_session}),
music_session.name,
music_session.pretty_scheduled_start(false),
notification.id,
@@ -925,12 +954,27 @@ module JamRuby
@@mq_router.publish_to_user(target_user.id, msg)
end
+ end
+ end
- begin
- UserMailer.scheduled_session_reminder(target_user, notification_msg, music_session).deliver
- rescue => e
- @@log.error("Unable to send SCHEDULED_SESSION_REMINDER email to user #{target_user.email} #{e}")
- end
+ # @param music_session - the session for which to send reminder
+ # @param reminder_type - the type of reminder; one of:
+ # => SCHEDULED_SESSION_REMINDER_DAY 24 hours
+ # => SCHEDULED_SESSION_REMINDER_UPCOMING 15 minutes
+ # => SCHEDULED_SESSION_REMINDER_IMMINENT 5 minutes (in-app)
+ def send_session_reminder(music_session, reminder_type)
+ raise ArgumentError, "Block required" unless block_given?
+ source_user = music_session.creator
+ rsvp_requests = RsvpRequest.index(music_session)
+ rsvp_requests.where(:canceled => false).each do |rsvp|
+ target_user = rsvp.user
+ notification = Notification.new
+ notification.description = reminder_type
+ notification.source_user_id = source_user.id
+ notification.target_user_id = target_user.id
+ notification.session_id = music_session.id
+ notification.save
+ yield(music_session, target_user, notification)
end
end
@@ -984,12 +1028,12 @@ module JamRuby
def send_band_session_join(music_session, band)
# if the session is private, don't send any notifications
- if music_session.musician_access || music_session.fan_access
+ if music_session.musician_access || music_session.fan_access
notification_msg = format_msg(NotificationTypes::BAND_SESSION_JOIN, {:band => band})
followers = band.followers.map { |bf| bf.user }
-
+
# do not send band session notifications to band members
followers = followers - band.users
@@ -1328,7 +1372,7 @@ module JamRuby
end
def send_band_invitation_accepted(band, band_invitation, sender, receiver)
-
+
notification = Notification.new
notification.band_id = band.id
notification.description = NotificationTypes::BAND_INVITATION_ACCEPTED
@@ -1362,7 +1406,7 @@ module JamRuby
msg = @@message_factory.musician_session_fresh(
music_session.id,
- user.id,
+ user.id,
user.name,
user.photo_url
)
diff --git a/ruby/lib/jam_ruby/models/rsvp_request.rb b/ruby/lib/jam_ruby/models/rsvp_request.rb
index f416640bf..20c3b9e9a 100644
--- a/ruby/lib/jam_ruby/models/rsvp_request.rb
+++ b/ruby/lib/jam_ruby/models/rsvp_request.rb
@@ -8,6 +8,7 @@ module JamRuby
validates :user, presence: true
validates :canceled, :inclusion => {:in => [nil, true, false]}
validate :creator_rsvp_cancel
+ before_save :cancel_calendar
# pulls all instruments from the associated rsvp_slots
def instrument_list
@@ -305,6 +306,15 @@ module JamRuby
errors.add(:canceled, "can't be canceled by the session organizer")
end
end
+
+ def cancel_calendar
+ calendar_manager = CalendarManager.new
+ if self.canceled
+ self.rsvp_slots.each do |slot|
+ calendar_manager.cancel_ics_event(slot.music_session, user)
+ end
+ end
+ end
end
end
diff --git a/ruby/lib/jam_ruby/models/score.rb b/ruby/lib/jam_ruby/models/score.rb
index f45248376..d19832c65 100644
--- a/ruby/lib/jam_ruby/models/score.rb
+++ b/ruby/lib/jam_ruby/models/score.rb
@@ -18,7 +18,7 @@ module JamRuby
raise "blocidispid must be positive" if blocidispid <= 0
raise "score must be positive" if score <= 0
ascore = Score.create(alocidispid: alocidispid, anodeid: anodeid, aaddr: aaddr, auserid: user_info[:auserid], alatencytestid: user_info[:alatencytestid], blocidispid: blocidispid, bnodeid: bnodeid, baddr: baddr, buserid: user_info[:buserid], blatencytestid: user_info[:blatencytestid], score: score, scorer: 0, score_dt: score_dt, scoring_data: score_data)
- bscore = Score.create(alocidispid: blocidispid, anodeid: bnodeid, aaddr: baddr, auserid: user_info[:buserid], blatencytestid: user_info[:blatencytestid], blocidispid: alocidispid, bnodeid: anodeid, baddr: aaddr, buserid: user_info[:auserid], blatencytestid: user_info[:alatencytestid], score: score, scorer: 1, score_dt: score_dt) if alocidispid != blocidispid
+ bscore = Score.create(alocidispid: blocidispid, anodeid: bnodeid, aaddr: baddr, auserid: user_info[:buserid], alatencytestid: user_info[:blatencytestid], blocidispid: alocidispid, bnodeid: anodeid, baddr: aaddr, buserid: user_info[:auserid], blatencytestid: user_info[:alatencytestid], score: score, scorer: 1, score_dt: score_dt) if alocidispid != blocidispid
Score.connection.execute("select update_current_network_scores(#{alocidispid}, #{blocidispid})")
return [ascore, bscore]
end
diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb
index 14dd0bf26..691f03a30 100644
--- a/ruby/lib/jam_ruby/models/user.rb
+++ b/ruby/lib/jam_ruby/models/user.rb
@@ -45,6 +45,9 @@ module JamRuby
# authorizations (for facebook, etc -- omniauth)
has_many :user_authorizations, :class_name => "JamRuby::UserAuthorization"
+ # calendars (for scheduling NOT in music_session)
+ has_many :calendars, :class_name => "JamRuby::Calendar"
+
# connections (websocket-gateway)
has_many :connections, :class_name => "JamRuby::Connection"
@@ -194,6 +197,11 @@ module JamRuby
validates_numericality_of :last_jam_audio_latency, greater_than:MINIMUM_AUDIO_LATENCY, less_than:MAXIMUM_AUDIO_LATENCY, :allow_nil => true
validates :last_jam_updated_reason, :inclusion => {:in => [nil, JAM_REASON_REGISTRATION, JAM_REASON_NETWORK_TEST, JAM_REASON_FTUE, JAM_REASON_JOIN, JAM_REASON_IMPORT, JAM_REASON_LOGIN] }
+ # stored in cents
+ validates_numericality_of :paid_sessions_hourly_rate, greater_than:0, less_than:200000, :if => :paid_sessions
+ # stored in cents
+ validates_numericality_of :paid_sessions_daily_rate, greater_than:0, less_than:5000000, :if => :paid_sessions
+
# custom validators
validate :validate_musician_instruments
validate :validate_current_password
@@ -699,6 +707,20 @@ module JamRuby
end
end
+ # Build calendars using given parameter.
+ # @param calendars (array of hash)
+ def update_calendars(calendars)
+ unless self.new_record?
+ Calendar.where("user_id = ?", self.id).delete_all
+ end
+
+ unless calendars.nil?
+ calendars.each do |cal|
+ self.calendars << self.calendars.create(cal)
+ end
+ end
+ end
+
# given an array of instruments, update a user's instruments
def update_instruments(instruments)
# delete all instruments for this user first
diff --git a/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb b/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb
new file mode 100644
index 000000000..388516441
--- /dev/null
+++ b/ruby/lib/jam_ruby/resque/scheduled/daily_job.rb
@@ -0,0 +1,17 @@
+module JamRuby
+ class DailyJob
+ extend Resque::Plugins::JamLonelyJob
+
+ @queue = :scheduled_daily_job
+ @@log = Logging.logger[DailyJob]
+
+ class << self
+ def perform
+ @@log.debug("waking up")
+ calendar_manager = CalendarManager.new
+ calendar_manager.cleanup()
+ @@log.debug("done")
+ end
+ end
+ end
+end
diff --git a/ruby/lib/jam_ruby/resque/scheduled/music_session_reminder.rb b/ruby/lib/jam_ruby/resque/scheduled/music_session_reminder.rb
new file mode 100644
index 000000000..503e31e9b
--- /dev/null
+++ b/ruby/lib/jam_ruby/resque/scheduled/music_session_reminder.rb
@@ -0,0 +1,31 @@
+require 'json'
+require 'resque'
+require 'resque-retry'
+require 'net/http'
+require 'digest/md5'
+
+module JamRuby
+ class MusicSessionReminder
+ extend Resque::Plugins::JamLonelyJob
+
+ @queue = :music_session_reminder
+
+ @@log = Logging.logger[MusicSessionReminder]
+
+ def self.lock_timeout
+ 120
+ end
+
+ def self.perform
+ @@log.debug("MusicSessionReminder waking up")
+
+ MusicSessionReminder.new.run
+
+ @@log.debug("MusicSessionReminder done")
+ end
+
+ def run
+ Notification.send_session_reminders()
+ end
+ end
+end
\ No newline at end of file
diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb
index 90cd48c44..531d3cc46 100644
--- a/ruby/spec/factories.rb
+++ b/ruby/spec/factories.rb
@@ -740,7 +740,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/calendar_manager_spec.rb b/ruby/spec/jam_ruby/calendar_manager_spec.rb
new file mode 100644
index 000000000..1532fca64
--- /dev/null
+++ b/ruby/spec/jam_ruby/calendar_manager_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+require 'icalendar'
+
+describe CalendarManager do
+ CALENDAR_NAME="Test Cal"
+
+ before :all do
+ @genre1 = FactoryGirl.create(:genre)
+ @calendar_manager = JamRuby::CalendarManager.new
+
+ # Time resolution is seconds:
+ @start = Time.at(Time.now.utc.to_i)
+ @stop =(@start+1.hours)
+ end
+
+ before(:each) do
+
+ end
+
+ describe "with music sessions" do
+ before :all do
+ @creator = FactoryGirl.create(:user)
+ @music_session = FactoryGirl.create(:music_session, :creator => @creator, :description => CALENDAR_NAME, :genre => @genre1, :scheduled_start=>@start, :scheduled_duration=>3600)
+ @music_session.reload
+ end
+
+ it "validator detects bad calendar" do
+ lambda{verify_ical("Bad medicine calendar")}
+ .should raise_error(RuntimeError)
+ end
+
+ it "can create calendar feed" do
+ ics = @calendar_manager.create_ics_feed(@creator)
+ # Basic format checking...there are some online tools that
+ # check a lot more, but no ruby libs that I could find:
+ lines = ics.split("\r\n")
+ lines.should have(12).items
+ lines.first.should eq("BEGIN:VCALENDAR")
+ lines.last.should eq("END:VCALENDAR")
+ lines[-2].should eq("END:VEVENT")
+ verify_ical(ics)
+ end
+ end
+
+ describe "with manual calendars" do
+ before :all do
+ @creator = FactoryGirl.create(:user)
+ @creator.calendars<CALENDAR_NAME, :description=>"This is a test", :start_at=>(@start), :end_at=>@stop, :trigger_delete=>false, :target_uid=>"2112"})
+ end
+
+ it "can create calendar feed" do
+ #pending "foobar"
+ ics = @calendar_manager.create_ics_feed(@creator)
+
+ # Basic format checking...there are some online tools that
+ # check a lot more, but no ruby libs that I could find:
+ lines = ics.split("\r\n")
+ lines.should have(12).items
+ lines.first.should eq("BEGIN:VCALENDAR")
+ lines.last.should eq("END:VCALENDAR")
+ lines[-2].should eq("END:VEVENT")
+ verify_ical(ics)
+ end
+ end
+
+ def verify_ical(ics)
+ strict_parser = Icalendar::Parser.new(ics, true)
+ cals = strict_parser.parse
+ cals.should_not be_nil
+ cals.should have(1).items
+
+ cal = cals.first
+ cal.should_not be_nil
+ cal.events.should have(1).items
+ event = cal.events.first
+ event.should_not be_nil
+
+ event.summary.should eq("JamKazam Session #{CALENDAR_NAME}")
+ event.dtstart.to_i.should_not be_nil
+ event.dtend.to_i.should_not be_nil
+ (event.dtstart).to_time.utc.to_i.should eq(@start.to_i)
+ (event.dtend).to_time.utc.to_i.should eq(@stop.to_i)
+ end
+end
+
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 ebcb92d2d..bc2c0c206 100644
--- a/ruby/spec/jam_ruby/models/band_filter_search_spec.rb
+++ b/ruby/spec/jam_ruby/models/band_filter_search_spec.rb
@@ -15,8 +15,12 @@ describe 'Band Search Model' do
let!(:to_join) { search.search_filter_for_subtype(BandSearch::TO_JOIN) }
let!(:to_hire) { search.search_filter_for_subtype(BandSearch::TO_HIRE) }
- before(:each) do
+ before(:all) do
+ Recording.delete_all
Band.delete_all
+ end
+
+ before(:each) do
@bands = []
@bands << @band1 = FactoryGirl.create(:band)
@bands << @band2 = FactoryGirl.create(:band)
diff --git a/ruby/spec/jam_ruby/models/band_search_spec.rb b/ruby/spec/jam_ruby/models/band_search_spec.rb
index 934f63dda..79b3759fb 100644
--- a/ruby/spec/jam_ruby/models/band_search_spec.rb
+++ b/ruby/spec/jam_ruby/models/band_search_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe User do
+describe "Band Search" do
let(:user) { FactoryGirl.create(:user) }
let(:band) { FactoryGirl.create(:band, name: "Example Band") }
@@ -15,6 +15,11 @@ describe User do
}
}
+ before(:all) do
+ Recording.delete_all
+ Band.delete_all
+ end
+
before(:each) do
@user = FactoryGirl.create(:user)
band.touch
diff --git a/ruby/spec/jam_ruby/models/band_spec.rb b/ruby/spec/jam_ruby/models/band_spec.rb
index 706f16691..6fed8ac1e 100644
--- a/ruby/spec/jam_ruby/models/band_spec.rb
+++ b/ruby/spec/jam_ruby/models/band_spec.rb
@@ -19,6 +19,17 @@ describe Band do
}
}
+ let(:band_params_no_genre) {
+ {
+ name: "The Band",
+ biography: "Biography",
+ city: 'Austin',
+ state: 'TX',
+ country: 'US',
+ validate_genres:true
+ }
+ }
+
describe 'with instruments' do
it 'builds with instruments' do
band.musician_instruments << FactoryGirl.build(:musician_instrument, player: band)
@@ -46,6 +57,9 @@ describe Band do
it "minimum genres" do
new_band.save.should be_false
new_band.errors[:genres].should == [ValidationMessages::BAND_GENRE_MINIMUM_NOT_MET]
+
+ new_band.genres = [Genre.first]
+ new_band.save.should be_true
end
it "maximum genres" do
@@ -56,6 +70,22 @@ describe Band do
end
describe "save" do
+ it "genres validate" do
+ band=Band.save(user, band_params_no_genre)
+ band.errors.any?.should be_true
+ band.errors[:genres].should == [ValidationMessages::BAND_GENRE_MINIMUM_NOT_MET]
+
+ band = Band.save(user, band_params)
+ band.errors.any?.should be_false
+
+ # Save again without a genre and make sure we get an error:
+ p = band_params_no_genre.clone
+ p[:id] = band.id
+ band = Band.save(user, p)
+ band.errors.any?.should be_true
+ band.errors[:genres].should == [ValidationMessages::BAND_GENRE_MINIMUM_NOT_MET]
+ end
+
it "can succeed" do
band = Band.save(user, band_params)
band.errors.any?.should be_false
@@ -67,6 +97,33 @@ describe Band do
band.country.should == band_params[:country]
end
+ it "saves current interests" do
+ parms = band_params
+ parms[:paid_gigs]=true
+ parms[:free_gigs]=false
+ parms[:hourly_rate]=5000
+ parms[:gig_minimum]=30000
+ parms[:add_new_members]=true
+ parms[:touring_option]=false
+ parms[:band_type]="virtual"
+ parms[:band_status]="amateur"
+ parms[:concert_count]=3
+
+ band = Band.save(user, parms)
+ band.errors.any?.should be_false
+
+ band.paid_gigs.should == true
+ band.free_gigs.should == false
+ band.hourly_rate.should == 5000
+ parms[:gig_minimum]=30000
+ band.add_new_members.should == true
+ band.touring_option.should == false
+ band.band_type.should == "virtual"
+ band.band_status.should == "amateur"
+ band.concert_count.should == 3
+ end
+
+
it "ensures user is a musician" do
expect{ Band.save(fan, band_params) }.to raise_error("must be a musician")
end
@@ -173,5 +230,5 @@ describe Band do
history = band.recent_history(nil, claimed_recording.id)
history.size.should == 0
end
- end
+ end
end
diff --git a/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb b/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb
index 4ec86db70..dc2fc47e3 100644
--- a/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb
+++ b/ruby/spec/jam_ruby/models/broadcast_notification_spec.rb
@@ -42,8 +42,10 @@ describe BroadcastNotification do
bns = BroadcastNotification.viewable_notifications(user1)
expect(bns.count).to be(3)
- expect(bns[0].id).to eq(broadcast3.id)
+ expect(bns[0].id).to eq(broadcast4.id)
expect(bns.detect {|bb| bb.id==broadcast2.id }).to be_nil
+ # now view broadcast 4, since it hasn't been seen, which should bring broadcast 3 up as next since it was seen longest ago
+ broadcast4.did_view(user1)
expect(BroadcastNotification.next_broadcast(user1).id).to eq(broadcast3.id)
end
diff --git a/ruby/spec/jam_ruby/models/feed_spec.rb b/ruby/spec/jam_ruby/models/feed_spec.rb
index fa4372a58..f24445637 100644
--- a/ruby/spec/jam_ruby/models/feed_spec.rb
+++ b/ruby/spec/jam_ruby/models/feed_spec.rb
@@ -2,6 +2,13 @@ require 'spec_helper'
describe Feed do
+ before(:all) do
+ MusicSession.delete_all
+ Recording.delete_all
+ IcecastMount.delete_all
+ end
+
+
let (:user1) { FactoryGirl.create(:user) }
let (:user2) { FactoryGirl.create(:user) }
let (:user3) { FactoryGirl.create(:user) }
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/ruby/spec/jam_ruby/models/musician_search_spec.rb b/ruby/spec/jam_ruby/models/musician_search_spec.rb
index da97f5eaa..012dbf482 100644
--- a/ruby/spec/jam_ruby/models/musician_search_spec.rb
+++ b/ruby/spec/jam_ruby/models/musician_search_spec.rb
@@ -17,6 +17,8 @@ describe 'Musician Search Model' do
describe "creates search obj" do
before(:all) do
User.delete_all
+ Score.connection.execute('delete from current_network_scores').check
+ Score.connection.execute('delete from scores').check
end
it "associates to user" do
@@ -132,12 +134,13 @@ describe 'Musician Search Model' do
describe "filters interests" do
before(:all) do
- MusicianSearch::INTEREST_VALS[1..-1].each do |val|
- user_types.each { |utype| FactoryGirl.create(utype, val => true) }
- end
+
end
it "get expected number per interest" do
+ MusicianSearch::INTEREST_VALS[1..-1].each do |val|
+ user_types.each { |utype| FactoryGirl.create(utype, val => true, :paid_sessions_hourly_rate=>2300, :paid_sessions_daily_rate=>15000) }
+ end
search.update_json_value(MusicianSearch::KEY_INTERESTS, MusicianSearch::INTEREST_VALS[1])
expect(search.do_search.count).to eq(user_types.count)
end
@@ -169,8 +172,8 @@ describe 'Musician Search Model' do
end
it "gets expected number of users" do
- instjson = [{ instrument_id: Instrument.first.id, proficiency_level: 2 },
- { instrument_id: Instrument.first(2)[1].id, proficiency_level: 2 }
+ instjson = [{ id: Instrument.first.id, level: 2 },
+ { id: Instrument.first(2)[1].id, level: 2 }
]
search.update_json_value(MusicianSearch::KEY_INSTRUMENTS, instjson)
expect(search.do_search.count).to eq(3)
@@ -201,6 +204,7 @@ describe 'Musician Search Model' do
describe "sort order by latency" do
before(:each) do
User.delete_all
+ Score.delete_all
t = Time.now - 10.minute
@user1 = FactoryGirl.create(:user, created_at: t+1.minute, last_jam_locidispid: 1)
@@ -232,12 +236,12 @@ describe 'Musician Search Model' do
Score.createx(2, 'b', 2, 4, 'd', 4, 70)
end
- it "sorts by latency" do
+ it "sorts by latency", intermittent: true do
search.update_json_value(MusicianSearch::KEY_SORT_ORDER, MusicianSearch::SORT_VALS[0])
results = search.do_search
- expect(results[0].id).to eq(@user1.id)
+ expect(results[0].id).to eq(@user1.id) # HAS FAILED HERE TOO
expect(results[1].id).to eq(@user2.id)
- expect(results[2].id).to eq(@user3.id)
+ expect(results[2].id).to eq(@user3.id) # HAS FAILED INTERMITTENTLY
expect(results[3].id).to eq(@user4.id)
end
@@ -286,28 +290,28 @@ describe 'Musician Search Model' do
selections.each do |hash|
search.update_json_value(hash[:key], hash[:value])
json_val = search.json_value(hash[:key])
- expect(search.description).to match(/; #{hash[:description]} = #{hash[:lookup][json_val]}/)
+ expect(search.description).to match(/ #{hash[:description]} = #{hash[:lookup][json_val]}/)
end
end
it 'has correct description for genres' do
search.update_json_value(MusicianSearch::KEY_GENRES, [Genre.first.id, Genre.last.id])
- expect(search.description).to match(/; Genres = #{Genre.first.description}, #{Genre.last.description}/)
+ expect(search.description).to match(/ Genres = #{Genre.first.description}, #{Genre.last.description}/)
end
it 'has correct description for ages' do
search.update_json_value(MusicianSearch::KEY_AGES, [MusicianSearch::AGE_COUNTS[0],MusicianSearch::AGE_COUNTS[1]])
- expect(search.description).to match(/; Ages = #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[0]]}, #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[1]]}/)
+ expect(search.description).to match(/ Ages = #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[0]]}, #{MusicianSearch::AGES[MusicianSearch::AGE_COUNTS[1]]}/)
end
it 'has correct description for instruments' do
instrs = Instrument.limit(2).order(:description)
- instjson = [{ instrument_id: instrs[0].id, proficiency_level: 2 },
- { instrument_id: instrs[1].id, proficiency_level: 1 }
+ instjson = [{ id: instrs[0].id, level: 2 },
+ { id: instrs[1].id, level: 1 }
]
search.update_json_value(MusicianSearch::KEY_INSTRUMENTS, instjson)
- instr_descrip = "#{instrs[0].description} (#{MusicianSearch::INSTRUMENT_PROFICIENCY[2]}), #{instrs[1].description} (#{MusicianSearch::INSTRUMENT_PROFICIENCY[1]})"
- expect(search.description).to match(/; Instruments = #{Regexp.escape(instr_descrip)}/)
+ instr_descrip = "#{instrs[0].description} / #{MusicianSearch::INSTRUMENT_PROFICIENCY[2]}, #{instrs[1].description} / #{MusicianSearch::INSTRUMENT_PROFICIENCY[1]}"
+ expect(search.description).to match(/ Instruments = #{Regexp.escape(instr_descrip)}/)
end
end
diff --git a/ruby/spec/jam_ruby/models/notification_spec.rb b/ruby/spec/jam_ruby/models/notification_spec.rb
index 1f201c142..fcfae98a1 100644
--- a/ruby/spec/jam_ruby/models/notification_spec.rb
+++ b/ruby/spec/jam_ruby/models/notification_spec.rb
@@ -18,6 +18,13 @@ describe Notification do
@session = FactoryGirl.create(:music_session)
@band = FactoryGirl.create(:band)
+ @slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('electric guitar'))
+ @slot1.save
+
+ @slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('drums'))
+ @slot2.save
+
+
@friend_request = FactoryGirl.create(:friend_request, user: @sender, friend: @receiver)
end
@@ -199,7 +206,7 @@ describe Notification do
it "does not send email when user is offline and opts out of emails" do
FactoryGirl.create(:friendship, :user => @receiver, :friend => @recording.owner)
FactoryGirl.create(:friendship, :user => @recording.owner, :friend => @receiver)
-
+
@receiver.subscribe_email = false
@receiver.save!
@@ -284,7 +291,7 @@ describe Notification do
@recording.band = @band
@recording.save!
-
+
follower.subscribe_email = false
follower.save!
@@ -671,35 +678,14 @@ describe Notification do
end
end
- describe "send scheduled session reminder" do
- # it "sends email when user is offline and subscribes to emails" do
- # session.creator = sender
- # session.save!
-
- # calls = count_publish_to_user_calls
- # notification = Notification.send_scheduled_session_cancelled(session)
-
- # UserMailer.deliveries.length.should == 1
- # calls[:count].should == 1
- # end
-
- # it "does not send email when user is offline and opts out of emails" do
- # session.creator = sender
- # session.save!
-
- # receiver.subscribe_email = false
- # receiver.save!
-
- # calls = count_publish_to_user_calls
- # notification = Notification.send_scheduled_session_cancelled(session)
-
- # UserMailer.deliveries.length.should == 0
- # calls[:count].should == 1
- # end
-
+ describe "reminders" do
+ let(:mail) { UserMailer.deliveries[0] }
+ before :each do
+ UserMailer.deliveries.clear
+ end
it "sends no notification if session is nil" do
calls = count_publish_to_user_calls
- notification = Notification.send_scheduled_session_reminder(nil)
+ notification = Notification.send_session_reminders()
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
@@ -707,12 +693,65 @@ describe Notification do
it "sends no notification if there are no rsvp requests" do
calls = count_publish_to_user_calls
- notification = Notification.send_scheduled_session_reminder(@session)
+ notification = Notification.send_session_reminders()
UserMailer.deliveries.length.should == 0
calls[:count].should == 0
end
- end
+
+ it "sends email 24 hours before" do
+ @session.creator = @sender
+ @session.scheduled_start = Time.now + 23.hours
+ @session.save!
+
+ notification = Notification.send_session_reminders()
+
+ UserMailer.deliveries.length.should == 1
+ calls = count_publish_to_user_calls
+ calls[:count].should == 0
+
+ mail.html_part.body.include?("is scheduled for tomorrow").should be_true
+ mail.text_part.body.include?("is scheduled for tomorrow").should be_true
+
+ mail.html_part.body.include?("starts in 1 hour").should be_false
+ mail.text_part.body.include?("starts in 1 hour").should be_false
+ end
+
+ it "sends email 1 hour before" do
+ @session.creator = @sender
+ @session.scheduled_start = Time.now + 59.minutes
+ @session.save!
+
+
+ notification = Notification.send_session_reminders()
+
+ UserMailer.deliveries.length.should == 1
+ calls = count_publish_to_user_calls
+ calls[:count].should == 0
+ mail.html_part.body.include?("is scheduled for tomorrow").should be_false
+ mail.text_part.body.include?("is scheduled for tomorrow").should be_false
+
+ mail.html_part.body.include?("starts in 1 hour").should be_true
+ mail.text_part.body.include?("starts in 1 hour").should be_true
+
+ end
+
+ it "sends notice 5 minutes before" do
+ UserMailer.deliveries.length.should == 0
+ receiver_connection = FactoryGirl.create(:connection, user: @receiver)
+ @receiver.reload
+
+ rsvp = RsvpRequest.create({:session_id => @session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "We be jammin!"}, @receiver)
+ UserMailer.deliveries.clear
+ calls = count_publish_to_user_calls
+ @session.creator = @sender
+ @session.scheduled_start = Time.now + 4.minutes
+ @session.save!
+ notification = Notification.send_session_reminders()
+ calls[:count].should == 1
+ UserMailer.deliveries.length.should == 0
+ end
+ end # reminders
describe "send scheduled session comment" do
# it "sends email when user is offline and subscribes to emails" do
diff --git a/ruby/spec/jam_ruby/models/online_presence_spec.rb b/ruby/spec/jam_ruby/models/online_presence_spec.rb
index 883917f25..db05c67aa 100644
--- a/ruby/spec/jam_ruby/models/online_presence_spec.rb
+++ b/ruby/spec/jam_ruby/models/online_presence_spec.rb
@@ -162,6 +162,10 @@ describe OnlinePresence do
let(:player1) { FactoryGirl.create(:user) }
let(:player2) { FactoryGirl.create(:user) }
end
+
+ after(:all) {
+ Band.delete_all
+ }
end
describe "with a band" do
@@ -169,5 +173,10 @@ describe OnlinePresence do
let(:player1) { FactoryGirl.create(:band) }
let(:player2) { FactoryGirl.create(:band) }
end
+
+ after(:all) {
+ Band.delete_all
+ }
+
end
end
\ No newline at end of file
diff --git a/ruby/spec/jam_ruby/models/performance_sample_spec.rb b/ruby/spec/jam_ruby/models/performance_sample_spec.rb
index 5cf592a2b..63f03f244 100644
--- a/ruby/spec/jam_ruby/models/performance_sample_spec.rb
+++ b/ruby/spec/jam_ruby/models/performance_sample_spec.rb
@@ -125,6 +125,15 @@ describe PerformanceSample do
let(:player1) { FactoryGirl.create(:user) }
let(:player2) { FactoryGirl.create(:user) }
end
+
+ after(:all) {
+ User.delete_all
+ RecordedTrack.delete_all
+ ClaimedRecording.delete_all
+ Recording.delete_all
+ ActiveMusicSession.delete_all
+ MusicSession.delete_all
+ }
end
describe "with a band" do
@@ -132,5 +141,14 @@ describe PerformanceSample do
let(:player1) { FactoryGirl.create(:band) }
let(:player2) { FactoryGirl.create(:band) }
end
+
+ after(:all) {
+ Band.delete_all
+ RecordedTrack.delete_all
+ ClaimedRecording.delete_all
+ Recording.delete_all
+ ActiveMusicSession.delete_all
+ MusicSession.delete_all
+ }
end
end
\ No newline at end of file
diff --git a/ruby/spec/jam_ruby/models/rsvp_request_spec.rb b/ruby/spec/jam_ruby/models/rsvp_request_spec.rb
index f93501647..9da353746 100644
--- a/ruby/spec/jam_ruby/models/rsvp_request_spec.rb
+++ b/ruby/spec/jam_ruby/models/rsvp_request_spec.rb
@@ -30,10 +30,10 @@ describe RsvpRequest do
@slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('electric guitar'))
@slot1.save
-
+
@slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @music_session, :instrument => JamRuby::Instrument.find('drums'))
@slot2.save
-
+
@invitation = FactoryGirl.build(:invitation, :sender => @session_creator, :receiver => @session_invitee, :music_session => @music_session)
@invitation.save
end
@@ -53,12 +53,12 @@ describe RsvpRequest do
@music_session.save
RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)
- expect {RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)}.to raise_error(JamRuby::StateError)
+ expect {RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id]}, @non_session_invitee)}.to raise_error(JamRuby::StateError)
end
it "should allow invitee to RSVP to session with closed RSVPs" do
rsvp = RsvpRequest.create({:session_id => @music_session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "We be jammin!"}, @session_invitee)
-
+
# verify comment
comment = SessionInfoComment.find_by_creator_id(@session_invitee)
comment.comment.should == "We be jammin!"
@@ -373,12 +373,14 @@ describe RsvpRequest do
comment = SessionInfoComment.find_by_creator_id(@session_invitee)
comment.comment.should == "Let's Jam!"
- # cancel
- expect {RsvpRequest.cancel({:id => rsvp.id, :session_id => @music_session.id, :cancelled => "all", :message => "Sorry, I'm bailing for all sessions"}, @session_invitee)}.to_not raise_error
+ calendar_count = Calendar.find(:all).count
+ # cancel & check that calendar has been added:
+ expect {RsvpRequest.cancel({:id => rsvp.id, :session_id => @music_session.id, :cancelled => "all", :message => "Sorry, I'm bailing for all sessions"}, @session_invitee)}.to_not raise_error
rsvp = RsvpRequest.find_by_id(rsvp.id)
rsvp.canceled.should == true
rsvp.cancel_all.should == true
+ (Calendar.find(:all).count - calendar_count).should eq(1)
# verify comment
comment = SessionInfoComment.find_by_creator_id(@session_invitee)
diff --git a/ruby/spec/jam_ruby/models/sale_spec.rb b/ruby/spec/jam_ruby/models/sale_spec.rb
index a0d4acf48..685d8dfbd 100644
--- a/ruby/spec/jam_ruby/models/sale_spec.rb
+++ b/ruby/spec/jam_ruby/models/sale_spec.rb
@@ -195,7 +195,9 @@ describe Sale do
end
- it "for a normally priced jam track" do
+ it "for a normally priced jam track", intermittent: true do
+ # intermittent: sometimes recurly won't mark it 'collected' soon enough for the test to pass
+
user.has_redeemable_jamtrack = false
user.save!
shopping_cart = ShoppingCart.create user, jamtrack, 1, false
@@ -236,6 +238,7 @@ describe Sale do
sale_line_item.recurly_adjustment_credit_uuid.should be_nil
sale_line_item.recurly_adjustment_uuid.should eq(user.jam_track_rights.last.recurly_adjustment_uuid)
+
# verify subscription is in Recurly
recurly_account = client.get_account(user)
adjustments = recurly_account.adjustments
diff --git a/ruby/spec/jam_ruby/resque/active_music_session_cleaner_spec.rb b/ruby/spec/jam_ruby/resque/active_music_session_cleaner_spec.rb
index 7bae67c59..2b9196f6f 100644
--- a/ruby/spec/jam_ruby/resque/active_music_session_cleaner_spec.rb
+++ b/ruby/spec/jam_ruby/resque/active_music_session_cleaner_spec.rb
@@ -3,6 +3,8 @@ require 'spec_helper'
describe "ActiveMusicSessionCleaner" do
before(:all) do
+ MusicSession.delete_all
+ ActiveMusicSession.delete_all
@cleaner = ActiveMusicSessionCleaner.new
@cleaner.interval = "INTERVAL '1 second'"
end
diff --git a/ruby/spec/jam_ruby/resque/music_session_reminder_spec.rb b/ruby/spec/jam_ruby/resque/music_session_reminder_spec.rb
new file mode 100644
index 000000000..35c8ae845
--- /dev/null
+++ b/ruby/spec/jam_ruby/resque/music_session_reminder_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe 'MusicSessionReminder' do
+ let(:mail) { UserMailer.deliveries[0] }
+ before :each do
+ UserMailer.deliveries.clear
+ MusicSession.delete_all
+ User.delete_all
+
+ @receiver = FactoryGirl.create(:user)
+ @sender = FactoryGirl.create(:user)
+ @session = FactoryGirl.create(:music_session)
+
+ @slot1 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('electric guitar'))
+ @slot1.save
+
+ @slot2 = FactoryGirl.build(:rsvp_slot, :music_session => @session, :instrument => JamRuby::Instrument.find('drums'))
+ @slot2.save
+ end
+
+ it "sends email 24 hours before" do
+ @session.creator = @sender
+ @session.scheduled_start = Time.now + 23.hours
+ @session.save!
+
+ JamRuby::MusicSessionReminder.perform
+
+ UserMailer.deliveries.length.should == 1
+ calls = count_publish_to_user_calls
+ calls[:count].should == 0
+
+ mail.html_part.body.include?("is scheduled for tomorrow").should be_true
+ mail.text_part.body.include?("is scheduled for tomorrow").should be_true
+
+ mail.html_part.body.include?("starts in 1 hour").should be_false
+ mail.text_part.body.include?("starts in 1 hour").should be_false
+ end
+
+ it "sends email 1 hour before" do
+ @session.creator = @sender
+ @session.scheduled_start = Time.now + 59.minutes
+ @session.save!
+
+
+ JamRuby::MusicSessionReminder.perform
+
+ UserMailer.deliveries.length.should == 1
+ calls = count_publish_to_user_calls
+ calls[:count].should == 0
+ mail.html_part.body.include?("is scheduled for tomorrow").should be_false
+ mail.text_part.body.include?("is scheduled for tomorrow").should be_false
+
+ mail.html_part.body.include?("starts in 1 hour").should be_true
+ mail.text_part.body.include?("starts in 1 hour").should be_true
+
+ end
+
+ it "sends notice 5 minutes before" do
+ UserMailer.deliveries.length.should == 0
+ receiver_connection = FactoryGirl.create(:connection, user: @receiver)
+ @receiver.reload
+
+ rsvp = RsvpRequest.create({:session_id => @session.id, :rsvp_slots => [@slot1.id, @slot2.id], :message => "We be jammin!"}, @receiver)
+ UserMailer.deliveries.clear
+ calls = count_publish_to_user_calls
+ @session.creator = @sender
+ @session.scheduled_start = Time.now + 4.minutes
+ @session.save!
+ JamRuby::MusicSessionReminder.perform
+ calls[:count].should == 1
+ UserMailer.deliveries.length.should == 0
+ end
+
+ def count_publish_to_user_calls
+ result = {count: 0}
+ MQRouter.any_instance.stub(:publish_to_user) do |receiver_id, msg|
+ result[:count] += 1
+ result[:msg] = msg
+ end
+ result
+ end
+end #spec
diff --git a/ruby/spec/jam_ruby/resque/music_session_scheduler_spec.rb b/ruby/spec/jam_ruby/resque/music_session_scheduler_spec.rb
index bc142cfda..77a0b4265 100644
--- a/ruby/spec/jam_ruby/resque/music_session_scheduler_spec.rb
+++ b/ruby/spec/jam_ruby/resque/music_session_scheduler_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe "MusicSessionScheduler" do
before(:all) do
+ MusicSession.delete_all
@scheduler = MusicSessionScheduler.new
end
diff --git a/ruby/spec/jam_ruby/resque/scheduled_daily_job_spec.rb b/ruby/spec/jam_ruby/resque/scheduled_daily_job_spec.rb
new file mode 100644
index 000000000..b53fdbca5
--- /dev/null
+++ b/ruby/spec/jam_ruby/resque/scheduled_daily_job_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe 'DailyJob' do
+ describe "calendar cleanup" do
+ shared_examples_for :calendar_cleanup do |trigger_delete, end_count|
+ before :each do
+ Calendar.destroy_all
+ @creator = FactoryGirl.create(:user)
+ @creator.calendars << Calendar.new(
+ :name=>"Test Cal",
+ :description=>"This is a test",
+ :start_at=>(Time.now),
+ :end_at=>Time.now,
+ :trigger_delete=>trigger_delete,
+ :target_uid=>"2112"
+ )
+ end
+
+ it "properly purges old 'delete' calendars" do
+ @creator.reload
+ @creator.calendars.should have(1).items
+
+ JamRuby::DailyJob.perform
+ @creator.reload
+ @creator.calendars.should have(1).items
+
+ Timecop.travel(Time.now + 5.weeks)
+ JamRuby::DailyJob.perform
+ @creator.reload
+ @creator.calendars.should have(end_count).items
+ Timecop.return
+ end
+ end
+
+ describe "whacks old 'delete' calendars" do
+ it_behaves_like :calendar_cleanup, true, 0
+ end
+
+ describe "doesn't whacks non 'delete' calendars" do
+ it_behaves_like :calendar_cleanup, false, 1
+ end
+ end # calendar cleanpu
+end #spec
diff --git a/ruby/spec/jam_ruby/resque/scheduled_music_session_cleaner_spec.rb b/ruby/spec/jam_ruby/resque/scheduled_music_session_cleaner_spec.rb
index 0913561ac..6d08c366e 100644
--- a/ruby/spec/jam_ruby/resque/scheduled_music_session_cleaner_spec.rb
+++ b/ruby/spec/jam_ruby/resque/scheduled_music_session_cleaner_spec.rb
@@ -2,14 +2,18 @@ require 'spec_helper'
describe 'ScheduledMusicSessionCleaner' do
+ before(:all) {
+ MusicSession.delete_all
+ }
+
it "purges old music sessions" do
[MusicSession::UNSTARTED_INTERVAL_DAYS_PURGE,
MusicSession::UNSTARTED_INTERVAL_DAYS_PURGE_RECUR].each do |interval|
dd = Time.now - (interval.to_i + 1).days
- Timecop.travel(dd)
+ Timecop.travel(dd)
FactoryGirl.create(:music_session, scheduled_start: dd)
Timecop.return
- end
+ end
msess2 = FactoryGirl.create(:music_session, scheduled_start: Time.now)
expect(MusicSession.count).to be(3)
diff --git a/ruby/spec/mailers/user_mailer_spec.rb b/ruby/spec/mailers/user_mailer_spec.rb
index c3d041060..320615020 100644
--- a/ruby/spec/mailers/user_mailer_spec.rb
+++ b/ruby/spec/mailers/user_mailer_spec.rb
@@ -123,7 +123,7 @@ describe UserMailer do
before(:each) do
user.update_email = "my_new_email@jamkazam.com"
- UserMailer.updating_email(user).deliver
+ UserMailer.updating_email(user).deliver
end
it { UserMailer.deliveries.length.should == 1 }
@@ -137,6 +137,48 @@ describe UserMailer do
it { mail.text_part.body.include?("to confirm your change in email").should be_true }
end
+ describe "notifications" do
+
+ let(:mail) { UserMailer.deliveries[0] }
+ let(:music_session) { FactoryGirl.create(:music_session) }
+
+ it "should send upcoming email" do
+ user.update_email = "my_new_email@jamkazam.com"
+ UserMailer.scheduled_session_reminder_upcoming(music_session.creator, music_session).deliver
+ UserMailer.deliveries.length.should == 1
+
+ mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
+ mail['to'].to_s.should == music_session.creator.email# rsvp_requests.first.user.email
+ mail.multipart?.should == true # because we send plain + htm
+
+ # verify that the messages are correctly configured
+ mail.html_part.body.include?("This is a reminder that your JamKazam session").should be_true
+ mail.text_part.body.include?("This is a reminder that your JamKazam session").should be_true
+ mail.html_part.body.include?("starts in 1 hour").should be_true
+ mail.text_part.body.include?("starts in 1 hour").should be_true
+
+
+ end
+
+ it "should send 1-day reminder" do
+ user.update_email = "my_new_email@jamkazam.com"
+ UserMailer.scheduled_session_reminder_day(music_session.creator, music_session).deliver
+ UserMailer.deliveries.length.should == 1
+
+ mail['from'].to_s.should == UserMailer::DEFAULT_SENDER
+ mail['to'].to_s.should == music_session.creator.email# rsvp_requests.first.user.email
+ mail.multipart?.should == true # because we send plain + htm
+
+ # verify that the messages are correctly configured
+ mail.html_part.body.include?("This is a reminder that your JamKazam session").should be_true
+ mail.text_part.body.include?("This is a reminder that your JamKazam session").should be_true
+ mail.html_part.body.include?("is scheduled for tomorrow").should be_true
+ mail.text_part.body.include?("is scheduled for tomorrow").should be_true
+
+ end
+ end
+
+
# describe "sends new musicians email" do
diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb
index 16ac84514..93ec5dda7 100644
--- a/ruby/spec/spec_helper.rb
+++ b/ruby/spec/spec_helper.rb
@@ -14,7 +14,7 @@ require 'resque_failed_job_mailer'
# to prevent embedded resque code from forking
ENV['FORK_PER_JOB'] = 'false'
-
+IS_BUILD_SERVER = !ENV['BUILD_SERVER'].nil?
# recreate test database and migrate it
SpecDb::recreate_database
@@ -85,9 +85,13 @@ end
config.run_all_when_everything_filtered = true
config.filter_run :focus
+ #config.formatter = :documentation
+
+
# you can mark a test as slow so that developers won't commonly hit it, but build server will http://blog.davidchelimsky.net/2010/06/14/filtering-examples-in-rspec-2/
config.filter_run_excluding slow: true unless run_tests? :slow
config.filter_run_excluding aws: true unless run_tests? :aws
+ config.filter_run_excluding intermittent: true if IS_BUILD_SERVER
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
@@ -108,7 +112,6 @@ end
config.before(:each) do
stub_const("APP_CONFIG", app_config)
-
end
config.after(:each) do
diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb
index 8d16dffbb..2717f63dc 100644
--- a/ruby/spec/support/utilities.rb
+++ b/ruby/spec/support/utilities.rb
@@ -206,6 +206,10 @@ def app_config
1
end
+ def google_public_server_key
+ "AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
+ end
+
private
def audiomixer_workspace_path
diff --git a/web/Gemfile b/web/Gemfile
index 0339472b2..924910f1b 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -18,10 +18,11 @@ else
end
end
+#gem 'license_finder'
gem 'oj', '2.10.2'
gem 'builder'
-gem 'rails', '~>3.2.11'
-gem 'railties', '~>3.2.11'
+gem 'rails', '~>3.2.22'
+gem 'railties', '~>3.2.22'
gem 'jquery-rails'
gem 'jquery-ui-rails', '4.2.1'
gem 'bootstrap-sass', '2.0.4'
@@ -36,7 +37,7 @@ gem 'pg', '0.17.1'
gem 'compass-rails', '1.1.3' # 1.1.4 throws an exception on startup about !initialize on nil
gem 'rabl', '0.11.0' # for JSON API development
gem 'gon', '~>4.1.0' # for passthrough of Ruby variables to Javascript variables
-gem 'eventmachine', '1.0.3'
+gem 'eventmachine', '1.0.4'
gem 'faraday', '~>0.9.0'
gem 'amqp', '0.9.8'
gem 'logging-rails', :require => 'logging/rails'
@@ -85,7 +86,7 @@ gem 'slim'
gem 'htmlentities'
gem 'sanitize'
gem 'recurly'
-gem 'guard', '2.7.3'
+#gem 'guard', '2.7.3'
gem 'influxdb', '0.1.8'
gem 'influxdb-rails', '0.1.10'
gem 'sitemap_generator'
@@ -95,18 +96,23 @@ gem 'react-rails', '~> 1.0'
source 'https://rails-assets.org' do
gem 'rails-assets-reflux'
+ gem 'rails-assets-classnames'
end
+#group :development, :production do
+# gem 'rack-timeout'
+#end
+
group :development, :test do
gem 'rspec-rails', '2.14.2'
gem "activerecord-import", "~> 0.4.1"
- gem 'guard-rspec', '0.5.5'
+# gem 'guard-rspec', '0.5.5'
# gem 'jasmine', '1.3.1'
gem 'pry'
gem 'execjs', '1.4.0'
gem 'factory_girl_rails', '4.1.0' # in dev because in use by rake task
gem 'database_cleaner', '1.3.0' #in dev because in use by rake task
-
+ gem 'test-unit'
# gem 'teaspoon'
# gem 'teaspoon-jasmine'
gem 'puma'
@@ -136,8 +142,8 @@ group :test, :cucumber do
#end
gem 'capybara-screenshot', '0.3.22' # 1.0.0 broke compat with rspec. maybe we need newer rspec
gem 'selenium-webdriver'
- gem 'cucumber-rails', :require => false #, '1.3.0', :require => false
- gem 'guard-spork', '0.3.2'
+# gem 'cucumber-rails', :require => false #, '1.3.0', :require => false
+# gem 'guard-spork', '0.3.2'
gem 'spork', '0.9.0'
gem 'launchy', '2.1.1'
gem 'rack-test'
diff --git a/web/app/assets/images/content/bkg_slider_gain_horiz_24.png b/web/app/assets/images/content/bkg_slider_gain_horiz_24.png
new file mode 100644
index 000000000..aad8fb7b9
Binary files /dev/null and b/web/app/assets/images/content/bkg_slider_gain_horiz_24.png differ
diff --git a/web/app/assets/images/content/icon_email.png b/web/app/assets/images/content/icon_email.png
new file mode 100644
index 000000000..a3163f217
Binary files /dev/null and b/web/app/assets/images/content/icon_email.png differ
diff --git a/web/app/assets/images/content/icon_equalizer.png b/web/app/assets/images/content/icon_equalizer.png
new file mode 100644
index 000000000..9c63c8dcc
Binary files /dev/null and b/web/app/assets/images/content/icon_equalizer.png differ
diff --git a/web/app/assets/images/content/icon_instrument_chat45.png b/web/app/assets/images/content/icon_instrument_chat45.png
new file mode 100644
index 000000000..4d0309da4
Binary files /dev/null and b/web/app/assets/images/content/icon_instrument_chat45.png differ
diff --git a/web/app/assets/images/content/icon_instrument_headphones21.png b/web/app/assets/images/content/icon_instrument_headphones21.png
new file mode 100644
index 000000000..79ef3c944
Binary files /dev/null and b/web/app/assets/images/content/icon_instrument_headphones21.png differ
diff --git a/web/app/assets/images/content/icon_instrument_headphones45.png b/web/app/assets/images/content/icon_instrument_headphones45.png
new file mode 100644
index 000000000..82533c0d2
Binary files /dev/null and b/web/app/assets/images/content/icon_instrument_headphones45.png differ
diff --git a/web/app/assets/images/content/icon_instrument_metronome21.png b/web/app/assets/images/content/icon_instrument_metronome21.png
new file mode 100644
index 000000000..362f08665
Binary files /dev/null and b/web/app/assets/images/content/icon_instrument_metronome21.png differ
diff --git a/web/app/assets/images/content/icon_instrument_metronome45.png b/web/app/assets/images/content/icon_instrument_metronome45.png
new file mode 100644
index 000000000..3c06b7544
Binary files /dev/null and b/web/app/assets/images/content/icon_instrument_metronome45.png differ
diff --git a/web/app/assets/images/content/icon_leave.png b/web/app/assets/images/content/icon_leave.png
new file mode 100644
index 000000000..1e2a53ff2
Binary files /dev/null and b/web/app/assets/images/content/icon_leave.png differ
diff --git a/web/app/assets/images/content/icon_mixer.png b/web/app/assets/images/content/icon_mixer.png
new file mode 100644
index 000000000..2de1cb8e2
Binary files /dev/null and b/web/app/assets/images/content/icon_mixer.png differ
diff --git a/web/app/assets/images/content/icon_mute_new.png b/web/app/assets/images/content/icon_mute_new.png
new file mode 100644
index 000000000..f198ae33d
Binary files /dev/null and b/web/app/assets/images/content/icon_mute_new.png differ
diff --git a/web/app/assets/images/content/icon_mute_sm.png b/web/app/assets/images/content/icon_mute_sm.png
new file mode 100644
index 000000000..e3d953460
Binary files /dev/null and b/web/app/assets/images/content/icon_mute_sm.png differ
diff --git a/web/app/assets/images/content/icon_open_folder.png b/web/app/assets/images/content/icon_open_folder.png
new file mode 100644
index 000000000..d4b9851ad
Binary files /dev/null and b/web/app/assets/images/content/icon_open_folder.png differ
diff --git a/web/app/assets/images/content/icon_pan.png b/web/app/assets/images/content/icon_pan.png
new file mode 100644
index 000000000..3599333cd
Binary files /dev/null and b/web/app/assets/images/content/icon_pan.png differ
diff --git a/web/app/assets/images/content/icon_record.png b/web/app/assets/images/content/icon_record.png
new file mode 100644
index 000000000..4ad12a2ae
Binary files /dev/null and b/web/app/assets/images/content/icon_record.png differ
diff --git a/web/app/assets/images/content/icon_resync.png b/web/app/assets/images/content/icon_resync.png
index 86ed62630..b9c1b410f 100644
Binary files a/web/app/assets/images/content/icon_resync.png and b/web/app/assets/images/content/icon_resync.png differ
diff --git a/web/app/assets/images/content/icon_settings_sm.png b/web/app/assets/images/content/icon_settings_sm.png
index 6f43ad376..b686ebecc 100644
Binary files a/web/app/assets/images/content/icon_settings_sm.png and b/web/app/assets/images/content/icon_settings_sm.png differ
diff --git a/web/app/assets/images/content/icon_share.png b/web/app/assets/images/content/icon_share.png
index 5c73cce88..9f2ba73a7 100644
Binary files a/web/app/assets/images/content/icon_share.png and b/web/app/assets/images/content/icon_share.png differ
diff --git a/web/app/assets/images/content/icon_sound.png b/web/app/assets/images/content/icon_sound.png
new file mode 100644
index 000000000..ececf76d5
Binary files /dev/null and b/web/app/assets/images/content/icon_sound.png differ
diff --git a/web/app/assets/images/content/icon_video.png b/web/app/assets/images/content/icon_video.png
new file mode 100644
index 000000000..f6da5cc1c
Binary files /dev/null and b/web/app/assets/images/content/icon_video.png differ
diff --git a/web/app/assets/images/content/icon_volume.png b/web/app/assets/images/content/icon_volume.png
new file mode 100644
index 000000000..339f21fd6
Binary files /dev/null and b/web/app/assets/images/content/icon_volume.png differ
diff --git a/web/app/assets/images/content/icon_volume_lg.png b/web/app/assets/images/content/icon_volume_lg.png
new file mode 100644
index 000000000..a99ca1b98
Binary files /dev/null and b/web/app/assets/images/content/icon_volume_lg.png differ
diff --git a/web/app/assets/images/web/button_cta_jamtrack_free.png b/web/app/assets/images/web/button_cta_jamtrack_free.png
new file mode 100644
index 000000000..0ffc3f32c
Binary files /dev/null and b/web/app/assets/images/web/button_cta_jamtrack_free.png differ
diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js
index 61b889eb2..ee570a513 100644
--- a/web/app/assets/javascripts/JamServer.js
+++ b/web/app/assets/javascripts/JamServer.js
@@ -198,50 +198,56 @@
function loggedIn(header, payload) {
- server.signedIn = true;
- server.clientID = payload.client_id;
- server.publicIP = payload.public_ip;
+ // reason for setTimeout:
+ // loggedIn causes an absolute ton of initialization to happen, and errors sometimes happen
+ // but because loggedIn(header,payload) is a callback from a websocket, the browser doesn't show a stack trace...
- if (context.jamClient !== undefined) {
- context.jamClient.connected = true;
- context.jamClient.clientID = server.clientID;
- }
+ setTimeout(function() {
+ server.signedIn = true;
+ server.clientID = payload.client_id;
+ server.publicIP = payload.public_ip;
- clearConnectTimeout();
+ if (context.jamClient !== undefined) {
+ context.jamClient.connected = true;
+ context.jamClient.clientID = server.clientID;
+ }
- heartbeatStateReset();
+ clearConnectTimeout();
- app.clientId = payload.client_id;
+ heartbeatStateReset();
- if(isClientMode()) {
- // tell the backend that we have logged in
- context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION
- $.cookie('client_id', payload.client_id);
- }
+ app.clientId = payload.client_id;
- // this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios
- // where there is no device on startup for the current profile.
- // So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
- // it to go through unless we've passed through .OnLoggedIn
- server.connected = true;
- server.reconnecting = false;
- server.connecting = false;
- initialConnectAttempt = false;
+ if (isClientMode()) {
+ // tell the backend that we have logged in
+ context.jamClient.OnLoggedIn(payload.user_id, payload.token); // ACTS AS CONTINUATION
+ $.cookie('client_id', payload.client_id);
+ }
- heartbeatMS = payload.heartbeat_interval * 1000;
- connection_expire_time = payload.connection_expire_time * 1000;
- logger.info("loggedIn(): clientId=" + app.clientId + " heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's');
- heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
- heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
- lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
- connectDeferred.resolve();
- $self.triggerHandler(EVENTS.CONNECTION_UP)
+ // this has to be after context.jamclient.OnLoggedIn, because it hangs in scenarios
+ // where there is no device on startup for the current profile.
+ // So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
+ // it to go through unless we've passed through .OnLoggedIn
+ server.connected = true;
+ server.reconnecting = false;
+ server.connecting = false;
+ initialConnectAttempt = false;
- activeElementEvent('afterConnect', payload);
+ heartbeatMS = payload.heartbeat_interval * 1000;
+ connection_expire_time = payload.connection_expire_time * 1000;
+ logger.info("loggedIn(): clientId=" + app.clientId + " heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's');
+ heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS);
+ heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000);
+ lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat
+ connectDeferred.resolve();
+ $self.triggerHandler(EVENTS.CONNECTION_UP)
- if(payload.client_update && context.JK.ClientUpdateInstance) {
- context.JK.ClientUpdateInstance.runCheck(payload.client_update.product, payload.client_update.version, payload.client_update.uri, payload.client_update.size)
- }
+ activeElementEvent('afterConnect', payload);
+
+ if (payload.client_update && context.JK.ClientUpdateInstance) {
+ context.JK.ClientUpdateInstance.runCheck(payload.client_update.product, payload.client_update.version, payload.client_update.uri, payload.client_update.size)
+ }
+ }, 0)
}
function heartbeatAck(header, payload) {
diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js
index 3d52d2139..c04a6b329 100644
--- a/web/app/assets/javascripts/accounts.js
+++ b/web/app/assets/javascripts/accounts.js
@@ -130,7 +130,7 @@
$('#account-content-scroller').on('click', '#account-my-jamtracks-link', function(evt) { evt.stopPropagation(); navToMyJamTracks(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-identity-link', function(evt) { evt.stopPropagation(); navToEditIdentity(); return false; } );
- $('#account-content-scroller').on('click', '#account-edit-profile-link', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } );
+ $('#account-content-scroller').on('click', '.account-edit-profile-link', function(evt) { evt.stopPropagation(); navToEditProfile(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-subscriptions-link', function(evt) { evt.stopPropagation(); navToEditSubscriptions(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-payments-link', function(evt) { evt.stopPropagation(); navToEditPayments(); return false; } );
$('#account-content-scroller').on('click', '#account-edit-audio-link', function(evt) { evt.stopPropagation(); navToEditAudio(); return false; } );
diff --git a/web/app/assets/javascripts/accounts_affiliate.js b/web/app/assets/javascripts/accounts_affiliate.js
index 21d0b8b1c..f9b5ec02a 100644
--- a/web/app/assets/javascripts/accounts_affiliate.js
+++ b/web/app/assets/javascripts/accounts_affiliate.js
@@ -207,7 +207,7 @@
rest.getLinks(type)
.done(populateLinkTable)
.fail(function() {
- app.notify({message: 'Unable to fetch links. Please try again later.' })
+ app.notify({text: 'Unable to fetch links. Please try again later.' })
})
}
}
diff --git a/web/app/assets/javascripts/accounts_jamtracks.js.coffee b/web/app/assets/javascripts/accounts_jamtracks.js.coffee
index 74706efe1..362a2e4d6 100644
--- a/web/app/assets/javascripts/accounts_jamtracks.js.coffee
+++ b/web/app/assets/javascripts/accounts_jamtracks.js.coffee
@@ -88,8 +88,8 @@ context.JK.AccountJamTracks = class AccountJamTracks
context.location = '/client#/session/' + newSessionId
# Re-loading the session settings will cause the form to reset with the right stuff in it.
# This is an extra xhr call, but it keeps things to a single codepath
- loadSessionSettings()
- context.JK.GA.trackSessionCount data.musician_access, data.fan_access, invitationCount
+ #loadSessionSettings()
+ context.JK.GA.trackSessionCount data.musician_access, data.fan_access, 0
context.JK.GA.trackSessionMusicians context.JK.GA.SessionCreationTypes.create
).fail (jqXHR) =>
handled = false
diff --git a/web/app/assets/javascripts/accounts_profile_interests.js b/web/app/assets/javascripts/accounts_profile_interests.js
index 6a4a65e2c..84b205777 100644
--- a/web/app/assets/javascripts/accounts_profile_interests.js
+++ b/web/app/assets/javascripts/accounts_profile_interests.js
@@ -145,15 +145,18 @@
$traditionalTouringOption.val(userDetail.traditional_band_touring ? '1' : '0')
context.JK.dropdown($traditionalTouringOption)
- $hourlyRate.val(userDetail.paid_sessions_hourly_rate)
- $dailyRate.val(userDetail.paid_sessions_daily_rate)
-
+ // convert the value to cents
+ $hourlyRate.val(profileUtils.normalizeMoneyForDisplay(userDetail.paid_sessions_hourly_rate));
+ $dailyRate.val(profileUtils.normalizeMoneyForDisplay(userDetail.paid_sessions_daily_rate));
+
$cowritingPurpose.val(userDetail.cowriting_purpose)
context.JK.dropdown($cowritingPurpose)
+
+ renderOptionalControls()
}
- function bindGenreSelector(type, $btnSelect, $genreList) {
- $btnSelect.unbind('click').bind('click', function(e) {
+ function bindGenreSelector(type, $btnSelect, $genreList) {
+ $btnSelect.unbind('click').bind('click', function(e) {
e.preventDefault()
var genreText = $genreList.html()
var genres = []
@@ -161,7 +164,7 @@
genres = genreText.split(GENRE_LIST_DELIMITER)
}
- ui.launchGenreSelectorDialog(type, genres, function(selectedGenres) {
+ ui.launchGenreSelectorDialog(type, genres, function(selectedGenres) {
$genreList.html(selectedGenres && selectedGenres.length > 0 ? selectedGenres.join(GENRE_LIST_DELIMITER) : NONE_SPECIFIED)
})
@@ -193,7 +196,8 @@
context.JK.dropdown($virtualBandCommitment)
context.JK.dropdown($traditionalBandCommitment)
- context.JK.dropdown($cowritingPurpose)
+ context.JK.dropdown($cowritingPurpose)
+ $screen.on('ifToggled', 'input[type="radio"].dependent-master', renderOptionalControls);
}
function enableSubmits() {
@@ -218,6 +222,52 @@
})
}
+ function isChecked(val) {
+ return (val && val != "false");
+ }
+
+ function renderOptionalControls(e) {
+ if(e){e.stopPropagation()}
+
+
+ // Is virtual band selected?
+ if (isChecked($screen.find($('input[name="virtual_band"]:checked')).val())) {
+ $screen.find($(".virtual-band-dependent")).removeClass("hidden")
+ } else {
+ $screen.find($(".virtual-band-dependent")).addClass("hidden")
+ }
+
+ // Is traditional band selected?
+ if (isChecked($screen.find($('input[name="traditional_band"]:checked')).val())) {
+ $screen.find($(".traditional-band-dependent")).removeClass("hidden")
+ } else {
+ $screen.find($(".traditional-band-dependent")).addClass("hidden")
+ }
+
+ // Is paid sessions selected?
+ if (isChecked($screen.find($('input[name="paid_sessions"]:checked')).val())) {
+ $screen.find($(".paid-sessions-dependent")).removeClass("hidden")
+ } else {
+ $screen.find($(".paid-sessions-dependent")).addClass("hidden")
+ }
+
+ // Is free sessions selected?
+ if (isChecked($screen.find($('input[name="free_sessions"]:checked')).val())) {
+ $screen.find($(".free-sessions-dependent")).removeClass("hidden")
+ } else {
+ $screen.find($(".free-sessions-dependent")).addClass("hidden")
+ }
+
+ // Is cowriting selected?
+ if (isChecked($screen.find($('input[name="cowriting"]:checked')).val())) {
+ $screen.find($(".cowriting-dependent")).removeClass("hidden")
+ } else {
+ $screen.find($(".cowriting-dependent")).addClass("hidden")
+ }
+
+ return false;
+ }
+
function navigateTo(targetLocation) {
context.location = targetLocation
}
@@ -238,11 +288,11 @@
paid_sessions: $screen.find('input[name=paid_sessions]:checked').val(),
paid_session_genres: $paidSessionsGenreList.html() === NONE_SPECIFIED ? [] : $paidSessionsGenreList.html().split(GENRE_LIST_DELIMITER),
- paid_sessions_hourly_rate: $hourlyRate.val(),
- paid_sessions_daily_rate: $dailyRate.val(),
+ paid_sessions_hourly_rate: profileUtils.normalizeMoneyForSubmit($hourlyRate.val()),
+ paid_sessions_daily_rate: profileUtils.normalizeMoneyForSubmit($dailyRate.val()),
free_sessions: $screen.find('input[name=free_sessions]:checked').val(),
- free_session_genre: $freeSessionsGenreList.html() === NONE_SPECIFIED ? [] : $freeSessionsGenreList.html().split(GENRE_LIST_DELIMITER),
+ free_session_genres: $freeSessionsGenreList.html() === NONE_SPECIFIED ? [] : $freeSessionsGenreList.html().split(GENRE_LIST_DELIMITER),
cowriting: $screen.find('input[name=cowriting]:checked').val(),
cowriting_genres: $cowritingGenreList.html() === NONE_SPECIFIED ? [] : $cowritingGenreList.html().split(GENRE_LIST_DELIMITER),
@@ -263,7 +313,12 @@
var errors = JSON.parse(xhr.responseText)
if(xhr.status == 422) {
+ context.JK.append_errors($hourlyRate, 'paid_sessions_hourly_rate', errors)
+ context.JK.append_errors($dailyRate, 'paid_sessions_daily_rate', errors)
+ if(errors.errors.length > 0) {
+ app.notifyServerError(xhr)
+ }
}
else {
app.ajaxError(xhr, textStatus, errorMessage)
@@ -279,7 +334,7 @@
app.bindScreen('account/profile/interests', screenBindings)
events()
-
+
$screen.find('.interest-options').iCheck({
checkboxClass: 'icheckbox_minimal',
radioClass: 'iradio_minimal',
@@ -288,11 +343,11 @@
profileUtils.initializeHelpBubbles($screen)
}
-
+
this.initialize = initialize
this.beforeShow = beforeShow
- this.afterShow = afterShow
+ this.afterShow = afterShow
return this
}
-
+
})(window,jQuery)
\ No newline at end of file
diff --git a/web/app/assets/javascripts/accounts_profile_samples.js b/web/app/assets/javascripts/accounts_profile_samples.js
index 6cce9de0e..0962d513b 100644
--- a/web/app/assets/javascripts/accounts_profile_samples.js
+++ b/web/app/assets/javascripts/accounts_profile_samples.js
@@ -20,6 +20,7 @@
var ui = new context.JK.UIHelper(JK.app);
var target = {};
var profileUtils = context.JK.ProfileUtils;
+
var $screen = $('.profile-online-sample-controls', parent);
// online presences
var $website = $screen.find('.website');
@@ -147,24 +148,29 @@
if (samples && samples.length > 0) {
$.each(samples, function(index, val) {
- recordingSources.push({
+ var source = {
'url': val.url,
'recording_id': val.service_id,
'recording_title': val.description
- });
-
- // TODO: this code is repeated in HTML file
- var recordingIdAttr = ' data-recording-id="' + val.service_id + '" ';
- var recordingUrlAttr = ' data-recording-url="' + val.url + '" ';
- var recordingTitleAttr = ' data-recording-title="' + val.description + '"';
- var title = formatTitle(val.description);
- $sampleList.append('
' + title + '
');
- $sampleList.append('
X
');
+ }
+ recordingSources.push(source);
+ buildNonJamKazamEntry($sampleList, type, source);
});
}
}
}
+ function buildNonJamKazamEntry($sampleList, type, source) {
+ // TODO: this code is repeated in HTML file
+ var recordingIdAttr = ' data-recording-id="' + source.recording_id + '" ';
+ var recordingUrlAttr = ' data-recording-url="' + source.url + '" ';
+ var recordingTitleAttr = ' data-recording-title="' + source.recording_title + '"';
+ var title = formatTitle(source.recording_title);
+ $sampleList.find(".empty").addClass("hidden")
+ $sampleList.append('
' + title + '
');
+ $sampleList.append('
X
');
+ }
+
function buildJamkazamEntry(recordingId, recordingName) {
var title = formatTitle(recordingName);
@@ -179,25 +185,22 @@
$btnAddJkRecording.click(function(evt) {
evt.preventDefault();
- // retrieve recordings and pass to modal dialog
- api.getClaimedRecordings()
- .done(function(response) {
- ui.launchRecordingSelectorDialog(response, jamkazamRecordingSources, function(selectedRecordings) {
- $jamkazamSampleList.empty();
+ ui.launchRecordingSelectorDialog(jamkazamRecordingSources, function(selectedRecordings) {
+ $jamkazamSampleList.empty();
- jamkazamRecordingSources = [];
+ jamkazamRecordingSources = [];
- // update the list with the selected recordings
- $.each(selectedRecordings, function(index, val) {
- jamkazamRecordingSources.push({
- 'claimed_recording_id': val.id,
- 'description': val.name
- });
-
- buildJamkazamEntry(val.id, val.name);
- });
+ // update the list with the selected recordings
+ $.each(selectedRecordings, function(index, val) {
+ jamkazamRecordingSources.push({
+ 'claimed_recording_id': val.id,
+ 'description': val.name
});
+
+ buildJamkazamEntry(val.id, val.name);
});
+ });
+
return false;
});
@@ -221,8 +224,8 @@
})
}
- function enableSubmits() {
- $btnSubmit.off("click").on("click", function(e) {
+ function enableSubmits() {
+ $btnSubmit.off("click").on("click", function(e) {
e.stopPropagation();
handleUpdateProfile();
return false;
@@ -287,6 +290,7 @@
disableSubmits()
var player = buildPlayer()
+
updateFn({
website: player.website,
online_presences: player.online_presences,
@@ -316,8 +320,13 @@
addPerformanceSamples(ps, $soundCloudSampleList, performanceSampleTypes.SOUNDCLOUD.description);
addPerformanceSamples(ps, $youTubeSampleList, performanceSampleTypes.YOUTUBE.description);
+ var website = $website.val()
+ if (website == '') {
+ website = null;
+ }
+
return {
- website: $website.val(),
+ website: website,
online_presences: op,
performance_samples: ps
}
@@ -428,8 +437,8 @@
siteSuccessCallback($inputDiv, youTubeRecordingValidator, $youTubeSampleList, 'youtube');
}
- function siteSuccessCallback($inputDiv, recordingSiteValidator, sampleList, type) {
- sampleList.find(".empty").addClass("hidden")
+ function siteSuccessCallback($inputDiv, recordingSiteValidator, $sampleList, type) {
+ $sampleList.find(".empty").addClass("hidden")
$inputDiv.removeClass('error');
$inputDiv.find('.error-text').remove();
@@ -437,13 +446,7 @@
if (recordingSources && recordingSources.length > 0) {
var addedRecording = recordingSources[recordingSources.length-1];
- // TODO: this code is repeated in elsewhere in this JS file:
- var recordingIdAttr = ' data-recording-id="' + addedRecording.recording_id + '" ';
- var recordingUrlAttr = ' data-recording-url="' + addedRecording.url + '" ';
- var recordingTitleAttr = ' data-recording-title="' + addedRecording.recording_title + '"';
- var title = formatTitle(addedRecording.recording_title);
- sampleList.append('
' + title + '
');
- sampleList.append('
X
');
+ buildNonJamKazamEntry($sampleList, type, addedRecording);
}
$inputDiv.find('input').val('');
@@ -457,7 +460,7 @@
});
//});
-
+
} // end initializeValidators.
@@ -471,7 +474,12 @@
'afterShow': afterShow
};
- app.bindScreen('account/profile/samples', screenBindings);
+ // We only want to bind this screen for accounts:
+ if (updateFn.name===api.updateUser.name) {
+ // A little hacky, but we are soon going to replace this:
+ app.bindScreen('account/profile/samples', screenBindings);
+ }
+
initializeValidators();
events();
}
diff --git a/web/app/assets/javascripts/accounts_video_profile.js b/web/app/assets/javascripts/accounts_video_profile.js
index fbb1a6d53..ef4d4cf63 100644
--- a/web/app/assets/javascripts/accounts_video_profile.js
+++ b/web/app/assets/javascripts/accounts_video_profile.js
@@ -11,12 +11,12 @@
'beforeHide':beforeHide
};
app.bindScreen('account/video', screenBindings);
-
- $webcamViewer.init($(".webcam-container"))
+
+ $webcamViewer.init($("#account-video-profile .webcam-container"))
}
function beforeShow() {
- $webcamViewer.beforeShow()
+ $webcamViewer.beforeShow()
}
function beforeHide() {
diff --git a/web/app/assets/javascripts/addNewGear.js b/web/app/assets/javascripts/addNewGear.js
index cbe9f7d50..ab45745fd 100644
--- a/web/app/assets/javascripts/addNewGear.js
+++ b/web/app/assets/javascripts/addNewGear.js
@@ -3,18 +3,16 @@
"use strict";
context.JK = context.JK || {};
- context.JK.AddNewGearDialog = function(app, sessionScreen) {
+ context.JK.AddNewGearDialog = function(app) {
var logger = context.JK.logger;
function events() {
$('#btn-leave-session-test').click(function() {
- sessionScreen.setPromptLeave(false);
+ context.SessionActions.leaveSession.trigger({location: '/client#/home'})
app.layout.closeDialog('configure-tracks');
- context.location = "/client#/home";
-
app.layout.startNewFtue();
});
diff --git a/web/app/assets/javascripts/addTrack.js b/web/app/assets/javascripts/addTrack.js
index ba6ccd459..00dd29aa6 100644
--- a/web/app/assets/javascripts/addTrack.js
+++ b/web/app/assets/javascripts/addTrack.js
@@ -161,7 +161,7 @@
/**
setTimeout(function() {
- var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 2);
+ var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 4);
// this is some ugly logic coming up, here's why:
// we need the id (guid) that the backend generated for the new track we just added
diff --git a/web/app/assets/javascripts/application.js b/web/app/assets/javascripts/application.js
index e77a3a58f..811277c13 100644
--- a/web/app/assets/javascripts/application.js
+++ b/web/app/assets/javascripts/application.js
@@ -38,6 +38,7 @@
//= require jquery.exists
//= require jquery.payment
//= require jquery.visible
+//= require classnames
//= require reflux
//= require howler.core.js
//= require jstz
@@ -54,11 +55,12 @@
//= require react
//= require react_ujs
//= require react-init
-//= require react-components
//= require web/signup_helper
//= require web/signin_helper
//= require web/signin
//= require web/tracking
+//= require webcam_viewer
+//= require react-components
//= require_directory .
//= require_directory ./dialog
//= require_directory ./wizard
diff --git a/web/app/assets/javascripts/backend_alerts.js b/web/app/assets/javascripts/backend_alerts.js
index da48d9138..348560630 100644
--- a/web/app/assets/javascripts/backend_alerts.js
+++ b/web/app/assets/javascripts/backend_alerts.js
@@ -37,20 +37,16 @@
}
function onGenericEvent(type, text) {
- context.setTimeout(function() {
- var alert = ALERT_TYPES[type];
- if(alert && alert.title) {
- app.notify({
- "title": ALERT_TYPES[type].title,
- "text": text,
- "icon_url": "/assets/content/icon_alert_big.png"
- });
- }
- else {
- logger.debug("Unhandled Backend Event type %o, data %o", type, text)
- }
- }, 1);
+ var alert = ALERT_TYPES[type];
+
+ if(alert && alert.title) {
+ context.NotificationActions.backendNotification({msg: alert.title, detail: alert.message, backend_detail:text, help: alert.help})
+ }
+ else {
+ logger.debug("Unhandled Backend Event type %o, data %o", type, text)
+ }
+
}
function alertCallback(type, text) {
@@ -77,8 +73,11 @@
}
if (type === 2) { // BACKEND_MIXER_CHANGE
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onBackendMixerChanged(type, text)
+
+ context.MixerActions.mixersChanged(type, text)
+
+ //if(context.JK.CurrentSessionModel)
+ // context.JK.CurrentSessionModel.onBackendMixerChanged(type, text)
}
else if (type === ALERT_NAMES.NO_VALID_AUDIO_CONFIG) { // NO_VALID_AUDIO_CONFIG
if(context.JK.GearUtilsInstance && context.JK.GearUtilsInstance.isRestartingAudio()) {
@@ -101,28 +100,36 @@
onStunEvent();
}
else if (type === 26) { // DEAD_USER_REMOVE_EVENT
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onDeadUserRemove(type, text);
+ MixerActions.deadUserRemove(text);
+ //if(context.JK.CurrentSessionModel)
+ // context.JK.CurrentSessionModel.onDeadUserRemove(type, text);
}
else if (type === 27) { // WINDOW_CLOSE_BACKGROUND_MODE
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onWindowBackgrounded(type, text);
+
+ SessionActions.windowBackgrounded()
+
+ //if(context.JK.CurrentSessionModel)
+ // context.JK.CurrentSessionModel.onWindowBackgrounded(type, text);
}
else if(type === ALERT_NAMES.SESSION_LIVEBROADCAST_FAIL) {
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onBroadcastFailure(type, text);
+ SessionActions.broadcastFailure(text)
+ //if(context.JK.CurrentSessionModel)
+ // context.JK.CurrentSessionModel.onBroadcastFailure(type, text);
}
else if(type === ALERT_NAMES.SESSION_LIVEBROADCAST_ACTIVE) {
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onBroadcastSuccess(type, text);
+ SessionActions.broadcastSuccess(text)
+ //if(context.JK.CurrentSessionModel)
+ // context.JK.CurrentSessionModel.onBroadcastSuccess(type, text);
}
else if(type === ALERT_NAMES.SESSION_LIVEBROADCAST_STOPPED) {
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onBroadcastStopped(type, text);
+ SessionActions.broadcastStopped(text)
+ //if(context.JK.CurrentSessionModel)
+ //context.JK.CurrentSessionModel.onBroadcastStopped(type, text);
}
else if(type === ALERT_NAMES.RECORD_PLAYBACK_STATE) {
- if(context.JK.CurrentSessionModel)
- context.JK.CurrentSessionModel.onPlaybackStateChange(type, text);
+ //if(context.JK.CurrentSessionModel)
+ // context.JK.CurrentSessionModel.onPlaybackStateChange(type, text);
+ context.MediaPlaybackActions.playbackStateChange(text);
}
else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) &&
(ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) {
diff --git a/web/app/assets/javascripts/bandProfile.js b/web/app/assets/javascripts/bandProfile.js
index b7501373b..668154b78 100644
--- a/web/app/assets/javascripts/bandProfile.js
+++ b/web/app/assets/javascripts/bandProfile.js
@@ -404,9 +404,6 @@
rest.getBandMembers(bandId, false)
.done(function(response) {
bindMusicians(response, false);
- if (isMember) {
- bindPendingMembers();
- }
})
.fail(function(xhr) {
if(xhr.status >= 500) {
@@ -437,6 +434,15 @@
}
function bindMusicians(musicians, isPending) {
+
+ if (!isPending && isMember) {
+ bindPendingMembers();
+ }
+
+ if(!isPending) {
+ $('#band-profile-members').empty(); // for race conditions seen most often in testing
+ }
+
$.each(musicians, function(index, musician) {
var instrumentLogoHtml = '';
if ("instruments" in musician && musician.instruments != null) {
diff --git a/web/app/assets/javascripts/band_setup.js b/web/app/assets/javascripts/band_setup.js
index 4e09a8e47..1147538a0 100644
--- a/web/app/assets/javascripts/band_setup.js
+++ b/web/app/assets/javascripts/band_setup.js
@@ -42,8 +42,9 @@
accountProfileSamples.initialize()
function navBack() {
+ var band = buildBand()
if (currentStep>0) {
- saveBand(function() {
+ saveBand(band, function() {
currentStep--
renderCurrentPage()
})
@@ -57,13 +58,21 @@
}
function navNext() {
+ var band = buildBand()
+ if (currentStep==GENRE_STEP) {
+ band.genres = getSelectedGenres();
+ band.validate_genres = true
+ } else {
+ band.validate_genres = false
+ }
+
if (currentStep
- @logger = context.JK.logger
@gearUtils = context.JK.GearUtils
@ALERT_NAMES = context.JK.ALERT_NAMES;
@lastCheckedBroadcast = null
@@ -21,7 +21,12 @@ context.JK.ClientInit = class ClientInit
this.watchBroadcast()
checkBroadcast: () =>
- broadcastActions.load.triggerPromise()
+ promise = broadcastActions.load.trigger()
+ if promise
+ promise.catch(() ->
+ false
+ )
+
watchBroadcast: () =>
if context.JK.currentUserId
diff --git a/web/app/assets/javascripts/dialog/commentDialog.js b/web/app/assets/javascripts/dialog/commentDialog.js
index 958d6ec27..ef8658f01 100644
--- a/web/app/assets/javascripts/dialog/commentDialog.js
+++ b/web/app/assets/javascripts/dialog/commentDialog.js
@@ -93,15 +93,24 @@
// $("#spnCommentCount").html(parseInt($("#spnCommentCount").text()) + 1);
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
- });
+ })
+ .fail(function(jqXHR) {
+ logger.error("unable to add recording comment: " + jqXHR.responseText)
+ app.notify({text: 'unable to add comment, please try again later.'})
+ })
}
else if (entityType === 'recording') {
+ logger.debug("recording comment logged! " + comment)
rest.addRecordingComment(recordingId, JK.currentUserId, comment)
.done(function(response) {
// $("#spnCommentCount", $scope).html(parseInt($("#spnCommentCount").text()) + 1);
renderComment(comment, context.JK.currentUserId, context.JK.currentUserName,
context.JK.currentUserAvatarUrl, $.timeago(Date.now()), context.JK.currentUserMusician, false);
- });
+ })
+ .fail(function(jqXHR) {
+ logger.error("unable to add recording comment: " + jqXHR.responseText)
+ app.notify({text: 'unable to add comment, please try again later.'})
+ })
}
}
}
diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js
index e4174df90..20801cfa6 100644
--- a/web/app/assets/javascripts/dialog/configureTrackDialog.js
+++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js
@@ -112,10 +112,14 @@
$voiceChatTabSelector.click(function () {
// validate audio settings
if (validateAudioSettings()) {
+ logger.debug("initializing voice chat helper")
configureTracksHelper.reset();
voiceChatHelper.reset();
showVoiceChatPanel();
}
+ else {
+ logger.debug("invalid audio settings; ignoring")
+ }
});
$btnCancel.click(function() {
diff --git a/web/app/assets/javascripts/dialog/localRecordingsDialog.js b/web/app/assets/javascripts/dialog/localRecordingsDialog.js
index 10fdae9a1..2402c0023 100644
--- a/web/app/assets/javascripts/dialog/localRecordingsDialog.js
+++ b/web/app/assets/javascripts/dialog/localRecordingsDialog.js
@@ -120,11 +120,11 @@
openingRecording = true;
// tell the server we are about to start a recording
- rest.startPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id})
+ rest.startPlayClaimedRecording({id: context.SessionStore.id(), claimed_recording_id: claimedRecording.id})
.done(function(response) {
// update session info
- context.JK.CurrentSessionModel.updateSession(response);
+ context.SessionActions.updateSession.trigger(response);
var recordingId = $(this).attr('data-recording-id');
var openRecordingResult = context.jamClient.OpenRecording(claimedRecording.recording);
@@ -138,7 +138,7 @@
"icon_url": "/assets/content/icon_alert_big.png"
});
- rest.stopPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id})
+ rest.stopPlayClaimedRecording({id: context.SessionStore.id(), claimed_recording_id: claimedRecording.id})
.fail(function(jqXHR) {
app.notify({
"title": "Couldn't Stop Recording Playback",
diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js
index bd0d136aa..7474c6a8a 100644
--- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js
+++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js
@@ -85,7 +85,7 @@
var backingTrack = $(this).data('server-model');
// tell the server we are about to open a backing track:
- rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.name})
+ rest.openBackingTrack({id: context.SessionStore.id(), backing_track_path: backingTrack.name})
.done(function(response) {
var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name, false);
@@ -99,7 +99,7 @@
// else {
// logger.error("unable to open backing track")
// }
- context.JK.CurrentSessionModel.refreshCurrentSession(true);
+ context.SessionActions.syncWithServer()
})
.fail(function(jqXHR) {
diff --git a/web/app/assets/javascripts/dialog/openJamTrackDialog.js b/web/app/assets/javascripts/dialog/openJamTrackDialog.js
index 867314632..15e46d6dc 100644
--- a/web/app/assets/javascripts/dialog/openJamTrackDialog.js
+++ b/web/app/assets/javascripts/dialog/openJamTrackDialog.js
@@ -86,10 +86,10 @@
var jamTrack = $(this).data('server-model');
// tell the server we are about to open a jamtrack
- rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
+ rest.openJamTrack({id: context.SessionStore.id(), jam_track_id: jamTrack.id})
.done(function(response) {
$dialog.data('result', {success:true, jamTrack: jamTrack})
- context.JK.CurrentSessionModel.updateSession(response);
+ context.SessionActions.updateSession.trigger(response);
app.layout.closeDialog('open-jam-track-dialog');
})
.fail(function(jqXHR) {
diff --git a/web/app/assets/javascripts/dialog/rateSessionDialog.js b/web/app/assets/javascripts/dialog/rateSessionDialog.js
index 644585bb5..6f501f645 100644
--- a/web/app/assets/javascripts/dialog/rateSessionDialog.js
+++ b/web/app/assets/javascripts/dialog/rateSessionDialog.js
@@ -54,6 +54,7 @@
function events() {
$('#btn-rate-session-cancel', $scopeSelector).click(function(evt) {
closeDialog();
+ return false;
});
$('#btn-rate-session-up', $scopeSelector).click(function(evt) {
if ($(this).hasClass('selected')) {
diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
index 6152927a2..cceaaedc2 100644
--- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
@@ -57,7 +57,7 @@
});
}
else if (localResult.aggregate_state == 'PARTIALLY_MISSING') {
- logger.error("unable to open recording due to some missing tracks: %o", localResults);
+ logger.error("unable to open recording due to some missing tracks: %o", recording, localResults);
app.notify({
title: "Unable to Open Recording for Playback",
text: "Some of your tracks associated with the recording are missing. This is a bug in the application.",
diff --git a/web/app/assets/javascripts/dialog/recordingSelectorDialog.js b/web/app/assets/javascripts/dialog/recordingSelectorDialog.js
index cc9e5dcf9..10e76595b 100644
--- a/web/app/assets/javascripts/dialog/recordingSelectorDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingSelectorDialog.js
@@ -2,7 +2,7 @@
"use strict";
context.JK = context.JK || {};
- context.JK.RecordingSelectorDialog = function(app, recordings, selectedRecordings, selectCallback) {
+ context.JK.RecordingSelectorDialog = function(app, selectedRecordings, selectCallback) {
var logger = context.JK.logger;
var rest = context.JK.Rest();
var recordingUtils = context.JK.RecordingUtils;
@@ -10,173 +10,26 @@
var dialogId = 'recording-selector-dialog';
var $screen = $('#' + dialogId);
var $btnSelect = $screen.find(".btn-select-recordings");
- var $instructions = $screen.find('#instructions');
var $recordings = $screen.find('.recordings');
+ var $paginatorHolder = null;
var feedHelper = new context.JK.Feed(app);
+ var $scroller = $recordings;
+ var $content = $recordings;
+ var $noMoreFeeds = $screen.find('.end-of-list');
+ var $empty = $();
+ feedHelper.initialize($screen, $scroller, $content, $noMoreFeeds, $empty, $empty, $empty, $empty, {sort: 'date', time_range: 'all', type: 'recording', show_checkbox: true, hide_avatar: true});
function beforeShow(data) {
+
}
function afterShow(data) {
- $recordings.empty();
-
- $.each(recordings, function(index, val) {
- bindRecordingItem(val);
- });
+ feedHelper.setUser(context.JK.currentUserId)
+ feedHelper.refresh()
// hide the avatars
- $screen.find('.avatar-small.ib').hide();
- }
-
- /********* THE FOLLOWING BLOCK IS REPEATED IN feedHelper.js **********/
- function startRecordingPlay($feedItem) {
- var img = $('.play-icon', $feedItem);
- var $controls = $feedItem.find('.recording-controls');
- img.attr('src', '/assets/content/icon_pausebutton.png');
- $controls.trigger('play.listenRecording');
- $feedItem.data('playing', true);
- }
-
- function stopRecordingPlay($feedItem) {
- var img = $('.play-icon', $feedItem);
- var $controls = $feedItem.find('.recording-controls');
- img.attr('src', '/assets/content/icon_playbutton.png');
- $controls.trigger('pause.listenRecording');
- $feedItem.data('playing', false);
- }
-
- function toggleRecordingPlay() {
-
- var $playLink = $(this);
- var $feedItem = $playLink.closest('.feed-entry');
- var playing = $feedItem.data('playing');
-
- if(playing) {
- stopRecordingPlay($feedItem);
- }
- else {
- startRecordingPlay($feedItem);
- }
- return false;
- }
-
- function toggleRecordingDetails() {
- var $detailsLink = $(this);
- var $feedItem = $detailsLink.closest('.feed-entry');
- var $musicians = $feedItem.find('.musician-detail');
- var $description = $feedItem.find('.description');
- var $name = $feedItem.find('.name');
- var toggledOpen = $detailsLink.data('toggledOpen');
-
- if(toggledOpen) {
- toggleClose($feedItem, $name, $description, $musicians)
- }
- else {
- toggleOpen($feedItem, $name, $description, $musicians)
- }
-
- toggledOpen = !toggledOpen;
- $detailsLink.data('toggledOpen', toggledOpen);
-
- return false;
- }
-
- function stateChangeRecording(e, data) {
- var $controls = data.element;
- var $feedItem = $controls.closest('.feed-entry');
-
- var $sliderBar = $('.recording-position', $feedItem);
- var $statusBar = $('.recording-status', $feedItem);
- var $currentTime = $('.recording-current', $feedItem);
- var $status = $('.status-text', $feedItem);
- var $playButton = $('.play-button', $feedItem);
-
- if(data.isEnd) stopRecordingPlay($feedItem);
- if(data.isError) {
- $sliderBar.hide();
- $playButton.hide();
- $currentTime.hide();
- $statusBar.show();
- $status.text(data.displayText);
- }
- }
-
- function toggleOpen($feedItem, $name, $description, $musicians) {
- $description.trigger('destroy.dot');
- $description.data('original-height', $description.css('height')).css('height', 'auto');
- $name.trigger('destroy.dot');
- $name.data('original-height', $name.css('height')).css('height', 'auto');
- $musicians.show();
- $feedItem.animate({'max-height': '1000px'});
- }
-
- function toggleClose($feedItem, $name, $description, $musicians, immediate) {
- $feedItem.css('height', $feedItem.height() + 'px')
- $feedItem.animate({'height': $feedItem.data('original-max-height')}, immediate ? 0 : 400).promise().done(function() {
- $feedItem.css('height', 'auto').css('max-height', $feedItem.data('original-max-height'));
-
- $musicians.hide();
- $description.css('height', $description.data('original-height'));
- $description.dotdotdot();
- $name.css('height', $name.data('original-height'));
- $name.dotdotdot();
- });
- }
- /**********************************************************/
-
- function bindRecordingItem(claimedRecording) {
- claimedRecording.recording.mix_info = recordingUtils.createMixInfo({state: claimedRecording.recording.mix_state});
- var options = {
- feed_item: claimedRecording.recording,
- candidate_claimed_recording: claimedRecording,
- mix_class: claimedRecording['has_mix?'] ? 'has-mix' : 'no-mix',
- };
-
- var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'}));
- var $controls = $feedItem.find('.recording-controls');
-
- var $titleText = $feedItem.find('.title .title-text');
-
- // if this item will be discarded, tack on a * to the RECORDING NAME
- var discardTime = claimedRecording.recording['when_will_be_discarded?'];
- if(discardTime) {
- context.JK.helpBubble($titleText, 'recording-discarded-soon', {discardTime: discardTime}, {});
- $titleText.text($titleText.text() + '*');
- }
-
- $controls.data('mix-state', claimedRecording.recording.mix_info); // for recordingUtils helper methods
- $controls.data('server-info', claimedRecording.recording.mix); // for recordingUtils helper methods
- $controls.data('view-context', 'feed');
-
- $('.timeago', $feedItem).timeago();
- context.JK.prettyPrintElements($('.duration', $feedItem));
- context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
- $('.details', $feedItem).click(toggleRecordingDetails);
- $('.details-arrow', $feedItem).click(toggleRecordingDetails);
- $('.play-button', $feedItem).click(toggleRecordingPlay);
-
- var checked = '';
-
- var match = $.grep(selectedRecordings, function(obj, index) {
- return obj.claimed_recording_id === claimedRecording.id;
- });
-
- if (match && match.length > 0) {
- checked = 'checked';
- }
-
- // put the item on the page
- $recordings.append("
");
- $recordings.append($feedItem);
-
- // these routines need the item to have height to work (must be after renderFeed)
- $controls.listenRecording({recordingId: claimedRecording.recording.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
- $controls.bind('statechange.listenRecording', stateChangeRecording);
- $('.dotdotdot', $feedItem).dotdotdot();
- $feedItem.data('original-max-height', $feedItem.css('height'));
- context.JK.bindHoverEvents($feedItem);
- context.JK.bindProfileClickEvents($feedItem);
+ //$screen.find('.avatar-small.ib').hide();
}
function afterHide() {
@@ -187,10 +40,10 @@
}
function events() {
- $btnSelect.click(function(evt) {
+ $btnSelect.off('click').on('click', function(evt) {
evt.preventDefault();
var preSelectedRecordings = [];
- $recordings.find('input[type=checkbox]:checked').each(function(index) {
+ $recordings.find('.select-box input[type=checkbox]:checked').each(function(index) {
preSelectedRecordings.push({
"id": $(this).attr('data-recording-id'),
"name": $(this).attr('data-recording-title')
@@ -198,6 +51,7 @@
});
if (selectCallback) {
+ console.log("calling selectCallback", preSelectedRecordings)
selectCallback(preSelectedRecordings);
}
@@ -217,8 +71,6 @@
app.bindDialog(dialogId, dialogBindings);
- $instructions.html('Select one or more recordings and click ADD to add JamKazam recordings to your performance samples.');
-
events();
}
diff --git a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js
index 9b29fcb28..d81b1de38 100644
--- a/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js
+++ b/web/app/assets/javascripts/dialog/rsvpSubmitDialog.js
@@ -10,6 +10,7 @@
var dialogId = 'rsvp-submit-dialog';
var $btnSubmit = $("#btnSubmitRsvp");
+
function beforeShow(data) {
$('.error', $dialog).hide();
}
@@ -56,7 +57,6 @@
$btnSubmit.unbind('click');
$btnSubmit.click(function(e) {
e.preventDefault();
-
var error = false;
var slotIds = [];
var selectedSlots = [];
@@ -96,7 +96,11 @@
if (!error) {
$dialog.triggerHandler(EVENTS.RSVP_SUBMITTED);
- app.layout.closeDialog(dialogId);
+
+ // Show confirmation & calendar; hide regular buttons.
+ $(".rsvp-options").addClass("hidden")
+ $(".rsvp-confirm").removeClass("hidden")
+ $(".buttons").addClass("hidden")
}
})
.fail(function(xhr, textStatus, errorMessage) {
diff --git a/web/app/assets/javascripts/dialog/sessionMasterMixDialog.js.coffee b/web/app/assets/javascripts/dialog/sessionMasterMixDialog.js.coffee
new file mode 100644
index 000000000..781031b95
--- /dev/null
+++ b/web/app/assets/javascripts/dialog/sessionMasterMixDialog.js.coffee
@@ -0,0 +1,36 @@
+$ = jQuery
+context = window
+context.JK ||= {}
+MIX_MODES = context.JK.MIX_MODES
+
+context.JK.SessionMasterMixDialog = class SessionMasterMixDialog
+ constructor: (@app) ->
+ @rest = context.JK.Rest()
+ @logger = context.JK.logger
+ @screen = null
+ @dialogId = 'session-master-mix-dialog'
+ @dialog = null
+ @closeBtn = null
+
+ initialize:() =>
+ dialogBindings =
+ 'beforeShow' : @beforeShow
+ 'afterShow' : @afterShow
+ 'afterHide' : @afterHide
+
+
+ @dialog = $('[layout-id="' + @dialogId + '"]')
+ @app.bindDialog(@dialogId, dialogBindings)
+ @content = @dialog.find(".dialog-inner")
+
+ beforeShow:() =>
+ @logger.debug("session-master-mix-dlg: beforeShow")
+ context.jamClient.SetMixerMode(MIX_MODES.MASTER)
+
+ afterShow:() =>
+ @logger.debug("session-master-mix-dlg: afterShow")
+
+ afterHide:() =>
+ context.jamClient.SetMixerMode(MIX_MODES.PERSONAL)
+
+
diff --git a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js
index 0c47eb20e..960d44cfd 100644
--- a/web/app/assets/javascripts/dialog/sessionSettingsDialog.js
+++ b/web/app/assets/javascripts/dialog/sessionSettingsDialog.js
@@ -1,16 +1,17 @@
(function(context,$) {
context.JK = context.JK || {};
- context.JK.SessionSettingsDialog = function(app, sessionScreen) {
+ context.JK.SessionSettingsDialog = function(app) {
var logger = context.JK.logger;
var gearUtils = context.JK.GearUtilsInstance;
var $dialog;
var $screen = $('#session-settings');
- var $selectedFilenames = $screen.find('#selected-filenames');
- var $uploadSpinner = $screen.find('.upload-spinner');
- var $selectedFilenames = $('#settings-selected-filenames');
+ //var $selectedFilenames = $screen.find('#selected-filenames');
+ var $uploadSpinner = $screen.find('.spinner-small');
+ //var $selectedFilenames = $('#settings-selected-filenames');
var $inputFiles = $screen.find('#session-select-files');
var $btnSelectFiles = $screen.find('.btn-select-files');
+ var $inputBox = $screen.find('.inputbox')
var rest = new JK.Rest();
var sessionId;
@@ -21,7 +22,7 @@
context.JK.GenreSelectorHelper.render('#session-settings-genre');
$dialog = $('[layout-id="session-settings"]');
- var currentSession = sessionScreen.getCurrentSession();
+ var currentSession = context.SessionStore.currentSession;
sessionId = currentSession.id;
// id
@@ -65,13 +66,21 @@
$('#session-settings-fan-access').val('listen-chat-band');
}
- // notation files
+ /**
+ // notation files in the account screen. ugh.
$selectedFilenames.empty();
for (var i=0; i < currentSession.music_notations.length; i++) {
var notation = currentSession.music_notations[i];
$selectedFilenames.append('' + notation.file_name + ' ');
+ }*/
+
+ $inputBox.empty();
+ for (var i=0; i < currentSession.music_notations.length; i++) {
+ var notation = currentSession.music_notations[i];
+ addNotation(notation)
}
+
context.JK.dropdown($('#session-settings-language'));
context.JK.dropdown($('#session-settings-musician-access'));
context.JK.dropdown($('#session-settings-fan-access'));
@@ -81,6 +90,29 @@
$('#session-settings-fan-access').easyDropDown(easyDropDownState)
}
+ function addNotation(notation) {
+
+ var $notation = $('
`
+
+ windowUnloaded: () ->
+ SessionActions.closeMedia(false) unless window.DontAutoCloseMedia
+
+ componentDidMount: () ->
+
+ $(window).unload(@windowUnloaded)
+
+ @root = jQuery(this.getDOMNode())
+
+ $loop = @root.find('input[name="loop"]')
+ context.JK.checkbox($loop)
+
+ $loop.on('ifChecked', () =>
+ logger.debug("@props", @props)
+ # it doesn't matter if you do personal or master, because backend just syncs both
+ MixerActions.loopChanged(@props.backingTracks[0].mixers.personal.mixer, true)
+ )
+ $loop.on('ifUnchecked', () =>
+ # it doesn't matter if you do personal or master, because backend just syncs both
+ MixerActions.loopChanged(@props.backingTracks[0].mixers.personal.mixer, false)
+ )
+
+ @resizeWindow()
+
+ # this is necessary due to whatever the client's rendering behavior is.
+ setTimeout(@resizeWindow, 300)
+
+ componentDidUpdate: () ->
+ @resizeWindow()
+
+ resizeWindow: () =>
+ $container = $('#minimal-container')
+ width = $container.width()
+ height = $container.height()
+
+ # there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
+ #mysteryTopMargin = 20
+ mysteryTopMargin = 0
+ # deal with chrome in real browsers
+ offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
+
+ # handle native client chrome that the above outer-inner doesn't catch
+ #if navigator.userAgent.indexOf('JamKazam') > -1
+
+ #offset += 25
+
+ window.resizeTo(width, height + offset)
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/PopupRecordingStartStop.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupRecordingStartStop.js.jsx.coffee
new file mode 100644
index 000000000..7cb29df6e
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/PopupRecordingStartStop.js.jsx.coffee
@@ -0,0 +1,135 @@
+context = window
+
+mixins = []
+
+# this check ensures we attempt to listen if this component is created in a popup
+if window.opener
+ mixins.push(Reflux.listenTo(window.opener.RecordingStore,"onRecordingStateChanged"))
+
+@PopupRecordingStartStop = React.createClass({
+
+ mixins: mixins
+
+ onRecordingStateChanged: (recordingState) ->
+ this.setState(isRecording: recordingState.isRecording, recordedOnce: this.state.recordedOnce || recordingState.isRecording)
+
+ startStopRecording: () ->
+ if this.state.isRecording
+ window.opener.RecordingActions.stopRecording()
+ else
+ window.opener.RecordingActions.startRecording()
+
+ onNoteShowHide: () ->
+ this.setState(showNote: !this.state.showNote)
+
+ getInitialState: () ->
+ {isRecording: window.ParentIsRecording, showNote: true, recordedOnce: false}
+
+ render: () ->
+
+ recordingVerb = if this.state.isRecording then 'Stop' else 'Start'
+
+ recordingBtnClasses = classNames({
+ "currently-recording" : this.state.isRecording,
+ "control" : true
+ })
+
+ noteJSX = `
+
+ Important Note
+
+
+ While playing in your session, you are listening to your own personal mix. This recording will use the master mix,
+ which may sound very different. To hear and adjust your master mix settings, click the MIXER button in the session toolbar.
+
+
`
+
+ recordingJSX = `
+
+
+
+
+
+
+
+
+
+
+
`
+
+ if this.state.showNote
+ noteText = 'hide note'
+ else
+ noteText = 'show note'
+
+ noteShowHideJSX = `{noteText}`
+
+ note = null
+ recordingOptions = null
+ noteShowHide = null
+
+ if this.state.showNote && !this.state.isRecording && !this.state.recordedOnce
+ # should we show the note itself? Only if not recording, too
+ note = noteJSX
+
+ if !this.state.isRecording && !this.state.recordedOnce
+ noteShowHide = noteShowHideJSX
+
+ if gon.global.video_available == "full"
+ recordingOptions = recordingJSX
+
+
+ `
+ You have not set up any inputs for your instrument or vocals.
+ If you want to hear yourself play through the JamKazam app,
+ and let the app mix your live playing with JamTracks, or with other musicians in online sessions,
+ click here now.
+
+ Use this slider to pan the audio of this track left or right in your personal mix.
+ This will not pan audio for other musicians in the session.
+ To pan audio in the master mix for recordings and broadcasts, use the Mixer button in the toolbar.
+
`
+ else
+
+ for i in [0..this.props.lightCount-1].reverse()
+ lightClass = if (i >= redSwitch) then "vu-red-off" else "vu-green-off"
+
+ lightClasses = classNames('vulight', 'vu' + i, lightClass)
+
+ lights.push(`
Preview "{this.props.jam_track.name}" Backing Track by {this.props.jam_track.original_artist}
+
Click the play buttons below to preview the master mix and fully isolated tracks of the professional backing track recording. All are included in your backing track.
+
+
+
+
+
+
+
+
`
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee b/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee
new file mode 100644
index 000000000..de888990e
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/landing/JamTrackCta.js.jsx.coffee
@@ -0,0 +1,60 @@
+context = window
+rest = context.JK.Rest()
+
+@JamTrackCta = React.createClass({
+
+ redeem: (e) ->
+ e.preventDefault()
+
+ return if @state.processing
+
+ isFree = context.JK.currentUserFreeJamTrack
+
+ rest.addJamtrackToShoppingCart({id: @props.jam_track.id}).done((response) =>
+ if(isFree)
+ if context.JK.currentUserId?
+ context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
+ context.location = '/client#/redeemComplete'
+ else
+ # now make a rest call to buy it
+ context.location = '/client#/redeemSignup'
+
+ else
+ context.location = '/client#/shoppingCart'
+
+ ).fail((jqXHR, textStatus, errorMessage) =>
+ if jqXHR.status == 422
+ errors = JSON.parse(jqXHR.responseText)
+ cart_errors = errors?.errors?.cart_id
+ if cart_errors?.length == 1 && cart_errors[0] == 'has already been taken'
+ context.location = '/client#/shoppingCart'
+ else
+ context.JK.app.ajaxError(jqXHR, textStatus, errorMessage)
+ @setState({processing:false})
+ )
+
+ @setState({processing:true})
+
+ getInitialState:() ->
+ {processing: false}
+
+ render: () ->
+ bandBrowseUrl = "/client?artist=#{this.props.jam_track.original_artist}#/jamtrackBrowse"
+
+ `