diff --git a/admin/app/admin/jam_tracks.rb b/admin/app/admin/jam_tracks.rb index 0f6d99e08..ed1e746ab 100644 --- a/admin/app/admin/jam_tracks.rb +++ b/admin/app/admin/jam_tracks.rb @@ -11,6 +11,18 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do form :partial => 'form' index do + + # default_actions # use this for all view/edit/delete links + column "Actions" do |jam_track| + links = ''.html_safe + clz = "member_link view_link show_tracks" + clz += ' expand' if params[:focus_track]==jam_track.id + links << link_to("Show Tracks", '#', :class => clz) + links << link_to("Update", edit_resource_path(jam_track), :class => "member_link edit_link") + links + end + + column :id column :name column :description @@ -40,18 +52,35 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do column :track_type column :instrument column :part - column :track do |track| + + column "" do |track| + if track.position > 1 + link_to 'Move Up', "jam_tracks/#{track.id}/move_up" + end + end + column "" do |track| + if track.position < jam_track.jam_track_tracks.count + link_to 'Move Down', "jam_tracks/#{track.id}/move_down" + end + end + column "" do |track| link_to 'Play', '#' end end end - # default_actions # use this for all view/edit/delete links - column "Actions" do |jam_track| - links = ''.html_safe - links << link_to("Show Tracks", '#', :class => "member_link view_link show_tracks") - links << link_to("Update", edit_resource_path(jam_track), :class => "member_link edit_link") - links - end + + end + + member_action :move_up, :method => :get do + track = JamTrackTrack.where("id=?",params[:id]).first + track.move_up + redirect_to("/admin/jam_tracks?focus_track=#{track.jam_track_id}", {:notice => "Moved Up."}) + end + + member_action :move_down, :method => :get do + track = JamTrackTrack.where("id=?",params[:id]).first + track.move_down + redirect_to("/admin/jam_tracks?focus_track=#{track.jam_track_id}", {:notice => "Moved Down."}) end end \ No newline at end of file diff --git a/admin/app/assets/javascripts/jam_track.js b/admin/app/assets/javascripts/jam_track.js index a8781672a..e1b6852bf 100644 --- a/admin/app/assets/javascripts/jam_track.js +++ b/admin/app/assets/javascripts/jam_track.js @@ -1,43 +1,48 @@ +function showTracks(rowJamTrack) { + var $jamTrackTracks = rowJamTrack.find("td.jam_track_tracks"); + + var name=rowJamTrack.find("td.name").text() + var count = $jamTrackTracks.find("table tbody tr").length; + + if (rowJamTrack.next().attr('id') == "jam_track_tracks_detail") { + $(this).html("Show Tracks"); + rowJamTrack.next().remove(); + } else { + $(this).html('Hide Tracks'); + if (count == 0) { + rowJamTrack.after( + $("").html( + $("") + ).append( + $("").html( + "No Tracks" + ) + ) + ); + } + else { + rowJamTrack.after( + $("").html( + $("Tracks in '" + name + "':") + ).append( + $("").html( + $jamTrackTracks.html() + ) + ) + ); + } + } +} + $(document).ready(function() { $("th.jam_track_tracks").css('display', 'none'); $("td.jam_track_tracks").css('display', 'none'); - + showTracks($("a.expand").parents("tr")) + $(".show_tracks").click(function(e) { e.preventDefault(); var $rowJamTrack = $(this).parents('tr'); - var $jamTrackTracks = $($rowJamTrack).find("td.jam_track_tracks"); - - var count = $jamTrackTracks.find("table tbody tr").length; - - if ($rowJamTrack.next().attr('id') == "jam_track_tracks_detail") { - $(this).html("Show Tracks"); - $rowJamTrack.next().remove(); - } - else { - $(this).html('Hide Tracks'); - if (count == 0) { - $rowJamTrack.after( - $("").html( - $("") - ).append( - $("").html( - "No Tracks" - ) - ) - ); - } - else { - $rowJamTrack.after( - $("").html( - $("") - ).append( - $("").html( - $jamTrackTracks.html() - ) - ) - ); - } - } + showTracks($rowJamTrack) }) }); \ No newline at end of file diff --git a/admin/app/views/admin/jam_tracks/_form.html.haml b/admin/app/views/admin/jam_tracks/_form.html.haml index 5c2028673..fadc93b32 100644 --- a/admin/app/views/admin/jam_tracks/_form.html.haml +++ b/admin/app/views/admin/jam_tracks/_form.html.haml @@ -2,25 +2,26 @@ = f.semantic_errors *f.object.errors.keys = f.inputs name: 'JamTrack fields' do - = f.input :name - = f.input :description + = f.input :name, :input_html => { :rows=>1, :maxlength=>200 } + = f.input :description, :input_html => { :rows=>5, :maxlength=>1000 } = f.input :bpm - = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES - = f.input :status, collection: JamRuby::JamTrack::STATUS - = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE - = f.input :original_artist - = f.input :songwriter - = f.input :publisher - = f.input :licensor, collection: JamRuby::JamTrackLicensor.all - = f.input :pro, collection: JamRuby::JamTrack::PRO - = f.input :genre, collection: JamRuby::Genre.all - = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION - = f.input :price + = f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false + = f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false + = f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false + = f.input :original_artist, :input_html => { :rows=>2, :maxlength=>200 } + = f.input :songwriter, :input_html => { :rows=>5, :maxlength=>1000 } + = f.input :publisher, :input_html => { :rows=>5, :maxlength=>1000 } + = f.input :licensor, collection: JamRuby::JamTrackLicensor.all, include_blank: false + = f.input :pro, collection: JamRuby::JamTrack::PRO, include_blank: false + = f.input :genre, collection: JamRuby::Genre.all, include_blank: false + = f.input :sales_region, collection: JamRuby::JamTrack::SALES_REGION, include_blank: false + = f.input :price, :required=>true, :input_html=>{type:'numeric'} = f.input :reproduction_royalty, :label => 'Reproduction Royalty' = f.input :public_performance_royalty, :label => 'Public Performance Royalty' - = f.input :reproduction_royalty_amount - = f.input :licensor_royalty_amount - = f.input :pro_royalty_amount + = f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'} + = f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'} + = f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'} + = f.input :plan_code, :label=>'Recurly Plan Code', :required=>true = f.input :url, :as => :file, :label => 'Audio File' = f.semantic_fields_for :jam_track_tracks do |track| diff --git a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml index 11200771b..a388e0f8e 100644 --- a/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml +++ b/admin/app/views/admin/jam_tracks/_jam_track_track_fields.html.haml @@ -1,9 +1,10 @@ = f.inputs name: 'Track fields' do %ol.nested-fields - = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE - = f.input :instrument, collection: Instrument.all - = f.input :part + = f.input :track_type, :as => :select, collection: JamRuby::JamTrackTrack::TRACK_TYPE, include_blank: false + = f.input :instrument, collection: Instrument.all, include_blank: false + = f.input :part, :required=>true, :input_html => { :rows=>1, :maxlength=>20, :type=>'numeric' } + = f.input :position - if f.object.new_record? diff --git a/admin/config/environments/production.rb b/admin/config/environments/production.rb index a071fa961..981c09d7c 100644 --- a/admin/config/environments/production.rb +++ b/admin/config/environments/production.rb @@ -19,7 +19,7 @@ JamAdmin::Application.configure do # Generate digests for assets URLs config.assets.digest = true - + # Defaults to nil and saved in location specified by config.assets.prefix # config.assets.manifest = YOUR_PATH @@ -73,4 +73,5 @@ JamAdmin::Application.configure do config.aws_bucket_public = 'jamkazam-public' config.aws_bucket = 'jamkazam' + end diff --git a/db/manifest b/db/manifest index 4777a1dc8..7337e35b6 100755 --- a/db/manifest +++ b/db/manifest @@ -233,4 +233,6 @@ add_track_resource_id.sql user_genres.sql user_online.sql icecast_source_changes.sql -diagnostics_user_id_index.sql \ No newline at end of file +diagnostics_user_id_index.sql +jam_track_updates.sql +private_key_in_jam_track_rights.sql diff --git a/db/up/jam_track_updates.sql b/db/up/jam_track_updates.sql new file mode 100644 index 000000000..3ee39e190 --- /dev/null +++ b/db/up/jam_track_updates.sql @@ -0,0 +1,44 @@ +-- Drop Jam Track Tracks constraints: +ALTER TABLE jam_track_tracks + DROP CONSTRAINT jam_track_tracks_jam_track_id_fkey; + +-- Drop Jam Track Tracks constraints: +ALTER TABLE jam_track_rights + DROP CONSTRAINT jam_track_rights_user_id_fkey, + DROP CONSTRAINT jam_track_rights_jam_track_id_fkey; + +-- Change Jam Tracks ID type to BIGINT so it can work like the other downloadable items: +CREATE SEQUENCE jam_tracks_next_seq; +ALTER TABLE jam_tracks + ALTER COLUMN id DROP DEFAULT, + ALTER COLUMN id TYPE BIGINT USING nextval('jam_tracks_next_seq'), + ALTER COLUMN id SET DEFAULT nextval('jam_tracks_next_seq'); + +-- Change referencing ID type and re-add constraints: +ALTER TABLE jam_track_tracks + ALTER COLUMN jam_track_id TYPE BIGINT USING 0, + ALTER COLUMN jam_track_id SET NOT NULL, + ADD CONSTRAINT jam_track_tracks_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE; + +-- Change referencing ID type and re-add constraints. Also +-- add S3 URL for user-specific downloads: +ALTER TABLE jam_track_rights + ALTER COLUMN id DROP DEFAULT, + ALTER COLUMN id TYPE BIGINT USING nextval('tracks_next_tracker_seq'), + ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'), + ADD COLUMN url VARCHAR(2048), + ADD COLUMN md5 VARCHAR, + ADD COLUMN length INTEGER NOT NULL DEFAULT 0, + ADD COLUMN download_count INTEGER NOT NULL DEFAULT 0, + ADD COLUMN signed BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN downloaded_since_sign BOOLEAN NOT NULL DEFAULT FALSE, + ADD COLUMN last_signed_at timestamp without time zone NULL, + ADD COLUMN last_downloaded_at timestamp without time zone NULL, + ADD COLUMN created_at timestamp without time zone NOT NULL, + ADD COLUMN updated_at timestamp without time zone NOT NULL, + ALTER COLUMN jam_track_id TYPE BIGINT USING 0, + ALTER COLUMN jam_track_id SET NOT NULL, + ADD CONSTRAINT jam_track_rights_user_id_fkey FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + ADD CONSTRAINT jam_track_rights_jam_track_id_fkey FOREIGN KEY(jam_track_id) REFERENCES jam_tracks(id) ON DELETE CASCADE; + +ALTER TABLE notifications ADD COLUMN jam_track_right_id BIGINT REFERENCES jam_track_rights(id); \ No newline at end of file diff --git a/db/up/private_key_in_jam_track_rights.sql b/db/up/private_key_in_jam_track_rights.sql new file mode 100644 index 000000000..6bd4ff223 --- /dev/null +++ b/db/up/private_key_in_jam_track_rights.sql @@ -0,0 +1 @@ +ALTER TABLE jam_track_rights ADD COLUMN private_key VARCHAR; \ No newline at end of file diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index b339465c7..252e9166b 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -78,6 +78,9 @@ message ClientMessage { SOURCE_UP = 252; SOURCE_DOWN = 253; + // jamtracks notifications + JAM_TRACK_SIGN_COMPLETE = 260; + TEST_SESSION_MESSAGE = 295; PING_REQUEST = 300; @@ -180,6 +183,9 @@ message ClientMessage { optional SourceUp source_up = 252; optional SourceDown source_down = 253; + // jamtracks notification + optional JamTrackSignComplete jam_track_sign_complete=260; + // Client-Session messages (to/from) optional TestSessionMessage test_session_message = 295; @@ -594,6 +600,11 @@ message SourceDown { optional string music_session = 1; // music session id } + +message JamTrackSignComplete { + required int32 jam_track_right_id = 1; // jam track right id +} + message SubscriptionMessage { optional string type = 1; // the type of the subscription optional string id = 2; // data about what to subscribe to, specifically diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 695dd0434..e26894ac4 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -55,6 +55,8 @@ require "jam_ruby/resque/scheduled/active_music_session_cleaner" require "jam_ruby/resque/scheduled/score_history_sweeper" require "jam_ruby/resque/scheduled/scheduled_music_session_cleaner" require "jam_ruby/resque/scheduled/recordings_cleaner" +require "jam_ruby/resque/scheduled/jam_tracks_cleaner" +require "jam_ruby/resque/jam_tracks_builder" require "jam_ruby/resque/scheduled/stats_maker" require "jam_ruby/resque/google_analytics_event" require "jam_ruby/resque/batch_email_job" @@ -75,6 +77,7 @@ require "jam_ruby/app/uploaders/mix_uploader" require "jam_ruby/app/uploaders/music_notation_uploader" require "jam_ruby/app/uploaders/jam_track_uploader" require "jam_ruby/app/uploaders/jam_track_track_uploader" +require "jam_ruby/app/uploaders/jam_track_right_uploader" require "jam_ruby/app/uploaders/max_mind_release_uploader" require "jam_ruby/lib/desk_multipass" require "jam_ruby/lib/ip" @@ -193,6 +196,7 @@ require "jam_ruby/models/jam_company" require "jam_ruby/models/user_sync" require "jam_ruby/models/video_source" require "jam_ruby/models/recorded_video" +require "jam_ruby/jam_tracks_manager" include Jampb diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb new file mode 100644 index 000000000..e33494063 --- /dev/null +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_right_uploader.rb @@ -0,0 +1,28 @@ +class JamTrackRightUploader < CarrierWave::Uploader::Base + # include CarrierWaveDirect::Uploader + include CarrierWave::MimeTypes + + process :set_content_type + + def initialize(*) + super + JamRuby::UploaderConfiguration.set_aws_private_configuration(self) + end + + # Add a white list of extensions which are allowed to be uploaded. + def extension_white_list + %w(jkz) + end + + def store_dir + nil + end + + def md5 + @md5 ||= ::Digest::MD5.file(current_path).hexdigest + end + + def filename + "#{model.store_dir}/#{model.filename}" if model.id + end +end diff --git a/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb b/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb index 784d646c4..98e555e3d 100644 --- a/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb +++ b/ruby/lib/jam_ruby/app/uploaders/jam_track_uploader.rb @@ -11,7 +11,7 @@ class JamTrackUploader < CarrierWave::Uploader::Base # Add a white list of extensions which are allowed to be uploaded. def extension_white_list - %w(jka) + %w(jkz) end def store_dir diff --git a/ruby/lib/jam_ruby/constants/notification_types.rb b/ruby/lib/jam_ruby/constants/notification_types.rb index 515f56d04..87dbe14e5 100644 --- a/ruby/lib/jam_ruby/constants/notification_types.rb +++ b/ruby/lib/jam_ruby/constants/notification_types.rb @@ -45,5 +45,7 @@ module NotificationTypes # general purpose text message TEXT_MESSAGE = "TEXT_MESSAGE" + # Jam Tracks: + JAM_TRACK_SIGN_COMPLETE = "JAM_TRACK_SIGN_COMPLETE" end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/jam_tracks_manager.rb b/ruby/lib/jam_ruby/jam_tracks_manager.rb new file mode 100644 index 000000000..e9518b549 --- /dev/null +++ b/ruby/lib/jam_ruby/jam_tracks_manager.rb @@ -0,0 +1,86 @@ +require 'json' +require 'tempfile' +require 'open3' +require 'fileutils' +require 'open-uri' + +module JamRuby + + # Interact with external python tools to create the JKZ + class JamTracksManager + class << self + def save_jam_track_jkz(user, jam_track) + jam_track_right = jam_track.right_for_user(user) + raise ArgumentError if jam_track_right.nil? + save_jam_track_right_jkz(jam_track_right) + end + + def save_jam_track_right_jkz(jam_track_right) + jam_track = jam_track_right.jam_track + #py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) + py_root = APP_CONFIG.jamtracks_dir + Dir.mktmpdir do |tmp_dir| + jam_file_opts="" + jam_track.jam_track_tracks.each do |jam_track_track| + nm=jam_track_track.filename + nm.gsub!(" ", "_") + track_filename = File.join(tmp_dir, nm) + track_url = jam_track_track.sign_url + copy_url_to_file(track_url, track_filename) + copy_url_to_file(track_url, File.join(".", nm)) + jam_file_opts << " -i '#{track_filename}+#{jam_track_track.part}'" + end + #puts "LS + " + `ls -la '#{tmp_dir}'` + + sku=jam_track.id + title=jam_track.name + output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz") + py_file = File.join(py_root, "jkcreate.py") + puts "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})" + + # From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819: + cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}'" + Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr| + pid = wait_thr.pid + exit_status = wait_thr.value + err = stderr.read(1000) + out = stdout.read(1000) + #puts "stdout: #{out}, stderr: #{err}" + raise ArgumentError, "Error calling python script: #{err}" if err.present? + raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file")) + jam_track_right[:url] + + #raise ArgumentError, "output_jkz is empty #{output_jkz}" unless File.exists?(output_jkz) + + jam_track_right.url.store!(File.open(output_jkz, "rb")) + jam_track_right.signed=true + jam_track_right.downloaded_since_sign=false + jam_track_right.private_key=File.read("#{tmp_dir}/skey.pem") + jam_track_right.save! + end + end # mktmpdir + jam_track_right + end # save_jam_track_jkz + + def copy_url_to_file(url, filename) + uri = URI(url) + open(filename, 'w+b') do |io| + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new uri + http.request request do |response| + response_code = response.code.to_i + unless response_code >= 200 && response_code <= 299 + puts "Response from server was #{response_code} / #{response.message}" + raise "bad status code: #{response_code}. body: #{response.body}" + end + response.read_body do |chunk| + io.write chunk + end + end + end + end + end # copy_url_to_file + + end # self + end # class +end # module diff --git a/ruby/lib/jam_ruby/lib/em_helper.rb b/ruby/lib/jam_ruby/lib/em_helper.rb index ba6e582e8..5867424ef 100644 --- a/ruby/lib/jam_ruby/lib/em_helper.rb +++ b/ruby/lib/jam_ruby/lib/em_helper.rb @@ -13,11 +13,15 @@ module JamWebEventMachine @@log = Logging.logger[JamWebEventMachine] + + # THIS WAS USED BY resque jobs needing EventMachine/AMQP, but it's no longer needed. It's useful code though + # starts amqp & eventmachine up first. # and then calls your block. # After the supplied block is done, # waits until all EM tasks scheduled in the supplied block are done, or timeout def self.run_wait_stop(timeout = 30, &blk) + JamWebEventMachine.run thread = Thread.current diff --git a/ruby/lib/jam_ruby/lib/stats.rb b/ruby/lib/jam_ruby/lib/stats.rb index bb73931f5..c51a14ae1 100644 --- a/ruby/lib/jam_ruby/lib/stats.rb +++ b/ruby/lib/jam_ruby/lib/stats.rb @@ -1,5 +1,41 @@ + require 'influxdb' +# monkey patch InfluxDB client to clear the queue when asked to stop +module InfluxDB + class Client + def stop! + @queue.clear if @queue + @stopped = true + end + end +end + +module InfluxDB + class Worker + def spawn_threads! + NUM_WORKER_THREADS.times do |thread_num| + log :debug, "Spawning background worker thread #{thread_num}." + + Thread.new do + Thread.current[:influxdb] = self.object_id + + at_exit do + log :debug, "Thread exiting, bailing out (not flushing queue)" + end + + while !client.stopped? + self.check_background_queue(thread_num) + sleep rand(SLEEP_INTERVAL) + end + end + end + + end + + end +end + module JamRuby class Stats @@ -10,6 +46,7 @@ module JamRuby def self.destroy! if @client + @client.queue.clear if @client.queue @client.stop! end end diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index 19be72de7..fab355772 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -712,6 +712,19 @@ module JamRuby ) end + def jam_track_sign_complete(receiver_id, jam_track_right_id) + signed = Jampb::JamTrackSignComplete.new( + :jam_track_right_id => jam_track_right_id + ) + + Jampb::ClientMessage.new( + :type => ClientMessage::Type::JAM_TRACK_SIGN_COMPLETE, + :route_to => USER_TARGET_PREFIX + receiver_id, #:route_to => CLIENT_TARGET, + :jam_track_sign_complete => signed + ) + end + + def recording_master_mix_complete(receiver_id, recording_id, claimed_recording_id, band_id, msg, notification_id, created_at) recording_master_mix_complete = Jampb::RecordingMasterMixComplete.new( :recording_id => recording_id, diff --git a/ruby/lib/jam_ruby/models/connection.rb b/ruby/lib/jam_ruby/models/connection.rb index 53714d6b8..8a813f9ce 100644 --- a/ruby/lib/jam_ruby/models/connection.rb +++ b/ruby/lib/jam_ruby/models/connection.rb @@ -229,11 +229,12 @@ module JamRuby CLIENT_TYPES.each do |type| stats[type] = 0 end - Connection.select('count(client_type) AS client_type_count, client_type') do |result| - stats[result['client_type']] = result['client_type_count'] + + Connection.select('count(client_type) AS client_type_count, client_type').group('client_type').all.each do |result| + stats[result['client_type']] = result['client_type_count'].to_i end - result = Connection.select('count(id) AS total, count(scoring_timeout) AS scoring_timeout_count, count(music_session_id) AS in_session, count(as_musician) AS musicians, count(udp_reachable) AS udp_reachable_count, count(is_network_testing) AS is_network_testing_count').first + result = Connection.select('count(id) AS total, count(CASE WHEN scoring_timeout > NOW() THEN 1 ELSE null END) AS scoring_timeout_count, count(music_session_id) AS in_session, count(as_musician) AS musicians, count(CASE WHEN udp_reachable THEN 1 ELSE null END) AS udp_reachable_count, count(CASE WHEN is_network_testing THEN 1 ELSE null END) AS is_network_testing_count').first stats['count'] = result['total'].to_i stats['scoring_timeout'] = result['scoring_timeout_count'].to_i diff --git a/ruby/lib/jam_ruby/models/jam_track.rb b/ruby/lib/jam_ruby/models/jam_track.rb index b3f11dbb1..c90281d48 100644 --- a/ruby/lib/jam_ruby/models/jam_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track.rb @@ -15,7 +15,7 @@ module JamRuby attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type, :original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price, :reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount, - :licensor_royalty_amount, :pro_royalty_amount, :jam_track_tracks_attributes, as: :admin + :licensor_royalty_amount, :pro_royalty_amount, :jam_track_tracks_attributes, :plan_code, as: :admin validates :name, presence: true, uniqueness: true, length: {maximum: 200} validates :description, length: {maximum: 1000} @@ -43,19 +43,47 @@ module JamRuby has_many :jam_track_tracks, :class_name => "JamRuby::JamTrackTrack", order: 'position ASC' - has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight", inverse_of: 'jam_track', :foreign_key => "jam_track_id" + has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true + class << self + def index(options = {}) + limit = options[:limit] + limit ||= 20 + limit = limit.to_i + + start = options[:start].presence + start = start.to_i || 0 + + query = JamTrack.joins(:jam_track_tracks) + .offset(start) + .limit(limit) + + query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? + 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? + query = query.group("jam_tracks.id") + + if query.length == 0 + [query, nil] + elsif query.length < limit + [query, nil] + else + [query, start + limit] + end + end + end + # create storage directory that will house this jam_track, as well as def store_dir - "jam_tracks/#{created_at.strftime('%m-%d-%Y')}/#{id}" + "jam_tracks/#{id}" end # create name of the file def filename - "#{name}.jka" + "#{name}.jkz" end # creates a short-lived URL that has access to the object. @@ -63,40 +91,49 @@ module JamRuby # 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/jka', :secure => false}) + s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/jkz', :secure => false}) end + def can_download?(user) owners.include?(user) end - def self.index user, options = {} - limit = options[:limit] - limit ||= 20 - limit = limit.to_i + def right_for_user(user) + jam_track_rights.where("user_id=?", user).first + end - start = options[:start].presence - start = start.to_i || 0 + def self.list_downloads(user, limit = 100, since = 0) + since = 0 unless since || since == '' # guard against nil + downloads = [] - query = JamTrack.joins(:jam_track_tracks) - .offset(start) - .limit(limit) - - query = query.where("jam_tracks.genre_id = '#{options[:genre]}'") unless options[:genre].blank? - 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? - query = query.group("jam_tracks.id") - - if query.length == 0 - [query, nil] - elsif query.length < limit - [query, nil] - else - [query, start + limit] + user.jam_track_rights + .limit(limit) + .where('jam_track_rights.id > ?', since) + .each do |jam_track_right| + downloads << { + :type => "jam_track", + :id => jam_track_right.id.to_s, + :jam_track_id => jam_track_right.jam_track_id, + :length => jam_track_right.length, + :md5 => jam_track_right.md5, + :url => jam_track_right.url, + :created_at => jam_track_right.created_at, + :next => jam_track_right.id + } end + + next_id = downloads[-1][:next] if downloads.length > 0 + next_id = since if next_id.nil? # echo back to the client the same value they passed in, if there are no results + + { + 'downloads' => downloads, + 'next' => next_id.to_s + } end - private + + private def sanitize_active_admin self.genre_id = nil if self.genre_id == '' diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 2a741a3a3..fbd97bc78 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -2,13 +2,94 @@ module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base - + include JamRuby::S3ManagerMixin + attr_accessible :user, :jam_track, :user_id, :jam_track_id, :url, :md5, :length, :download_count belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" validates :user, presence:true validates :jam_track, presence:true + validate :verify_download_count validates_uniqueness_of :user_id, scope: :jam_track_id + + # Uploads the JKZ: + mount_uploader :url, JamTrackRightUploader + before_destroy :delete_s3_files + + MAX_JAM_TRACK_DOWNLOADS = 1000 + + def store_dir + "#{jam_track.store_dir}/rights" + end + + # create name of the file + def filename + "#{jam_track.name}.jkz" + end + + def verify_download_count + if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin + errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") + end + end + + def self.ready_to_clean + JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000) + end + + def finish_sign(length, md5) + self.last_signed_at = Time.now + self.length = length + self.md5 = md5 + self.signed = true + if save + Notification.send_jam_track_sign_complete(self) + else + raise "Error sending notification #{self.errors}" + end + 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, :secure => false}) + end + + def delete_s3_files + remove_url! + end + + def enqueue + begin + Resque.enqueue(JamTracksBuilder, self.id) + rescue Exception => e + # implies redis is down. we don't update started_at by bailing out here + false + end + + # avoid db validations + JamTrackRight.where(:id => self.id).update_all(:last_downloaded_at => Time.now) + + true + end + + def update_download_count(count=1) + self.download_count = self.download_count + count + self.last_downloaded_at = Time.now + end + + def self.list_keys(user, jamtracks) + if jamtracks.nil? + return [] + end + + JamTrack.select('jam_tracks.id, jam_track_rights.private_key AS private_key, jam_track_rights.id AS jam_track_right_id') + .joins("LEFT OUTER JOIN jam_track_rights ON jam_tracks.id = jam_track_rights.jam_track_id AND jam_track_rights.user_id = '#{user.id}'") + .where('jam_tracks.id IN (?)', jamtracks) + end + 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 f9e8ebcb9..978ed29ab 100644 --- a/ruby/lib/jam_ruby/models/jam_track_track.rb +++ b/ruby/lib/jam_ruby/models/jam_track_track.rb @@ -9,7 +9,7 @@ module JamRuby mount_uploader :url, JamTrackTrackUploader - attr_accessible :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin + attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, :url, as: :admin validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000} validates :part, length: {maximum: 20} @@ -43,5 +43,52 @@ module JamRuby # 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 - end -end + + def move_up + #normalize_position + if self.position > 1 + # Switch with previous + previous_track = self.jam_track.jam_track_tracks.where("position=?", self.position-1).first + if previous_track + JamTrack.transaction do + previous_track.position,self.position = self.position,previous_track.position + previous_track.save(validate:false) + self.save(validate:false) + end + end + end + end + + def move_down + count=normalize_position + if self.position < count + # Switch with next: + next_track = self.jam_track.jam_track_tracks.where("position=?", self.position+1).first + if next_track + next_track.position,self.position = self.position,next_track.position + next_track.save(validate:false) + self.save(validate:false) + end + end + end + + private + def normalize_position + parent = self.jam_track + position = 0 + if parent + JamTrack.transaction do + parent.jam_track_tracks.each do |jtt| + position += 1 + if jtt.position != position + jtt.position = position + jtt.save(validate:false) + end + end + end + end + position + end # normalize_position + + end # class +end # module diff --git a/ruby/lib/jam_ruby/models/music_session.rb b/ruby/lib/jam_ruby/models/music_session.rb index e1cbea90e..ff4b2e3ea 100644 --- a/ruby/lib/jam_ruby/models/music_session.rb +++ b/ruby/lib/jam_ruby/models/music_session.rb @@ -286,12 +286,15 @@ module JamRuby # keep unstarted sessions around for 12 hours after scheduled_start session_not_started = "(music_sessions.scheduled_start > NOW() - '12 hour'::INTERVAL AND music_sessions.started_at IS NULL)" + # keep started sessions that are not finished yet + session_started_not_finished = "(music_sessions.started_at IS NOT NULL AND music_sessions.session_removed_at IS NULL)" + # let session be restarted for up to 2 hours after finishing session_finished = "(music_sessions.session_removed_at > NOW() - '2 hour'::INTERVAL)" query = MusicSession.where("music_sessions.canceled = FALSE") query = query.where("music_sessions.user_id = '#{user.id}'") - query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished}") + query = query.where("music_sessions.scheduled_start IS NULL OR #{session_not_started} OR #{session_finished} OR #{session_started_not_finished}") query = query.where("music_sessions.create_type IS NULL OR music_sessions.create_type != '#{CREATE_TYPE_QUICK_START}'") query = query.order("music_sessions.scheduled_start ASC") diff --git a/ruby/lib/jam_ruby/models/notification.rb b/ruby/lib/jam_ruby/models/notification.rb index 79187c3a2..783f39fd2 100644 --- a/ruby/lib/jam_ruby/models/notification.rb +++ b/ruby/lib/jam_ruby/models/notification.rb @@ -13,6 +13,7 @@ module JamRuby belongs_to :band, :class_name => "JamRuby::Band", :foreign_key => "band_id" belongs_to :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => "music_session_id" belongs_to :recording, :class_name => "JamRuby::Recording", :foreign_key => "recording_id" + belongs_to :jam_track_right, :class_name => "JamRuby::JamTrackRight", :foreign_key => "jam_track_right_id" validates :target_user, :presence => true validates :message, length: {minimum: 1, maximum: 400}, no_profanity: true, if: :text_message? @@ -203,6 +204,9 @@ module JamRuby when NotificationTypes::SCHEDULED_SESSION_COMMENT return "New message about session." + when NotificationTypes::JAM_TRACK_SIGN_COMPLETE + return "Jam Track is ready for download." + # recording notifications when NotificationTypes::MUSICIAN_RECORDING_SAVED return "#{name} has made a new recording." @@ -1186,6 +1190,19 @@ module JamRuby end end + def send_jam_track_sign_complete(jam_track_right) + + notification = Notification.new + notification.jam_track_right_id = jam_track_right.id + notification.description = NotificationTypes::JAM_TRACK_SIGN_COMPLETE + notification.target_user_id = jam_track_right.user_id + notification.save! + + msg = @@message_factory.jam_track_sign_complete(jam_track_right.user_id, jam_track_right.id) + @@mq_router.publish_to_user(jam_track_right.user_id, msg) + #@@mq_router.publish_to_all_clients(msg) + end + def send_client_update(product, version, uri, size) msg = @@message_factory.client_update( product, version, uri, size) diff --git a/ruby/lib/jam_ruby/models/shopping_cart.rb b/ruby/lib/jam_ruby/models/shopping_cart.rb index ccad1b9d4..da1e567f6 100644 --- a/ruby/lib/jam_ruby/models/shopping_cart.rb +++ b/ruby/lib/jam_ruby/models/shopping_cart.rb @@ -13,7 +13,7 @@ module JamRuby def product_info product = self.cart_product - {name: product.name, price: product.price} unless product.nil? + {name: product.name, price: product.price, product_id: cart_id} unless product.nil? end def cart_product diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 63890f2e7..c35db3dd2 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -1477,7 +1477,6 @@ module JamRuby def self.stats stats = {} result = User.select('count(CASE WHEN musician THEN 1 ELSE null END) as musician_count, count(CASE WHEN musician = FALSE THEN 1 ELSE null END) as fan_count, count(first_downloaded_client_at) first_downloaded_client_at_count, count(first_ran_client_at) first_ran_client_at_count, count(first_certified_gear_at) first_certified_gear_at_count, count(first_music_session_at) as first_music_session_at_count, count(first_invited_at) first_invited_at_count, count(first_friended_at) as first_friended_at_count, count(first_social_promoted_at) first_social_promoted_at_count, avg(last_jam_audio_latency) last_jam_audio_latency_avg').first - puts "result #{result['musician_count']}" stats['musicians'] = result['musician_count'].to_i stats['fans'] = result['fan_count'].to_i stats['downloaded_client'] = result['first_downloaded_client_at_count'].to_i diff --git a/ruby/lib/jam_ruby/resque/audiomixer.rb b/ruby/lib/jam_ruby/resque/audiomixer.rb index 7ea7e7e55..cbe62d92e 100644 --- a/ruby/lib/jam_ruby/resque/audiomixer.rb +++ b/ruby/lib/jam_ruby/resque/audiomixer.rb @@ -20,13 +20,11 @@ module JamRuby def self.perform(mix_id, postback_ogg_url, postback_mp3_url) - JamWebEventMachine.run_wait_stop do - audiomixer = AudioMixer.new() - audiomixer.postback_ogg_url = postback_ogg_url - audiomixer.postback_mp3_url = postback_mp3_url - audiomixer.mix_id = mix_id - audiomixer.run - end + audiomixer = AudioMixer.new() + audiomixer.postback_ogg_url = postback_ogg_url + audiomixer.postback_mp3_url = postback_mp3_url + audiomixer.mix_id = mix_id + audiomixer.run end diff --git a/ruby/lib/jam_ruby/resque/batch_email_job.rb b/ruby/lib/jam_ruby/resque/batch_email_job.rb index 6d82d420e..fbfcde05f 100644 --- a/ruby/lib/jam_ruby/resque/batch_email_job.rb +++ b/ruby/lib/jam_ruby/resque/batch_email_job.rb @@ -3,7 +3,7 @@ require 'resque-lonely_job' module JamRuby class BatchEmailJob - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @@log = Logging.logger[BatchEmailJob] diff --git a/ruby/lib/jam_ruby/resque/icecast_config_writer.rb b/ruby/lib/jam_ruby/resque/icecast_config_writer.rb index 05ce40a26..50b41f230 100644 --- a/ruby/lib/jam_ruby/resque/icecast_config_writer.rb +++ b/ruby/lib/jam_ruby/resque/icecast_config_writer.rb @@ -9,7 +9,7 @@ module JamRuby # executes a mix of tracks, creating a final output mix class IcecastConfigWriter - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @@log = Logging.logger[IcecastConfigWriter] diff --git a/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb new file mode 100644 index 000000000..cc782a5ec --- /dev/null +++ b/ruby/lib/jam_ruby/resque/jam_tracks_builder.rb @@ -0,0 +1,38 @@ +require 'json' +require 'resque' +require 'resque-retry' +require 'net/http' +require 'digest/md5' + +module JamRuby + class JamTracksBuilder + extend JamRuby::ResqueStats + + @queue = :jam_tracks_builder + + def log + @log || Logging.logger[JamTracksBuilder] + end + + attr_accessor :jam_track_right_id + + def self.perform(jam_track_right_id) + jam_track_builder = JamTracksBuilder.new() + jam_track_builder.jam_track_right_id = jam_track_right_id + jam_track_builder.run + end + + def run + log.info("jam_track_builder job starting. jam_track_right_id #{jam_track_right_id}") + @jam_track_right = JamTrackRight.find(jam_track_right_id) + JamRuby::JamTracksManager.save_jam_track_right_jkz(@jam_track_right) + + length = @jam_track_right.url.size() + md5 = Digest::MD5.new + + @jam_track_right.finish_sign(length, md5.to_s) + + log.info "Signed jamtrack to #{@jam_track_right[:url]}" + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/resque/quick_mixer.rb b/ruby/lib/jam_ruby/resque/quick_mixer.rb index daddc1802..93704173f 100644 --- a/ruby/lib/jam_ruby/resque/quick_mixer.rb +++ b/ruby/lib/jam_ruby/resque/quick_mixer.rb @@ -19,12 +19,10 @@ module JamRuby def self.perform(quick_mix_id, postback_mp3_url) - JamWebEventMachine.run_wait_stop do - audiomixer = QuickMixer.new - audiomixer.postback_mp3_url = postback_mp3_url - audiomixer.quick_mix_id = quick_mix_id - audiomixer.run - end + audiomixer = QuickMixer.new + audiomixer.postback_mp3_url = postback_mp3_url + audiomixer.quick_mix_id = quick_mix_id + audiomixer.run end diff --git a/ruby/lib/jam_ruby/resque/resque_hooks.rb b/ruby/lib/jam_ruby/resque/resque_hooks.rb index 37685f0d8..fcd344bf6 100644 --- a/ruby/lib/jam_ruby/resque/resque_hooks.rb +++ b/ruby/lib/jam_ruby/resque/resque_hooks.rb @@ -1,26 +1,52 @@ require 'resque' -# https://devcenter.heroku.com/articles/forked-pg-connections -Resque.before_fork do - defined?(ActiveRecord::Base) and - ActiveRecord::Base.connection.disconnect! +ENV['FORK_PER_JOB'] = 'false' - JamRuby::Stats.destroy! +def shutdown + puts "Cleaning up resources..." + Stats.destroy! + EventMachine.stop_event_loop + puts "Terminated!" + exit! end -Resque.after_fork do - defined?(ActiveRecord::Base) and - ActiveRecord::Base.establish_connection +Resque.before_first_fork do + JamWebEventMachine.start + #ActiveRecord::Base.establish_connection config = { influxdb_database: APP_CONFIG.influxdb_database, influxdb_username: APP_CONFIG.influxdb_username, influxdb_password: APP_CONFIG.influxdb_password, influxdb_hosts: APP_CONFIG.influxdb_hosts, influxdb_port: APP_CONFIG.influxdb_port, - influxdb_async: false # if we use async=true, the forked job will die before the stat is sent + influxdb_async: true # if we use async=true, the forked job will die before the stat is sent } + + # handle these events and force a shutdown. this is required I think due to influxdb-client. + Signal.trap("TERM") do + shutdown + end + Signal.trap("INT") do + shutdown + end + JamRuby::Stats.init(config) + +end +# https://devcenter.heroku.com/articles/forked-pg-connections +Resque.before_fork do + + #defined?(ActiveRecord::Base) and + # ActiveRecord::Base.connection.disconnect! + + #JamRuby::Stats.destroy! +end + +Resque.after_fork do + #defined?(ActiveRecord::Base) and + # ActiveRecord::Base.establish_connection + end # for jobs that do not extend lonely job, just extend this module and get stats @@ -36,19 +62,21 @@ module JamRuby end end + +require 'resque-lonely_job' + # for jobs that extend lonely job, we override around_perform already implemented in LonelyJob, and call into it module Resque module Plugins - module LonelyJob + module JamLonelyJob def around_perform(*args) Stats.timer('job.stats') do - begin - yield - ensure - unlock_queue(*args) - end + super end end end end end + +Resque::Plugins::JamLonelyJob.module_eval { include Resque::Plugins::LonelyJob } + diff --git a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb index e700a0427..899f3dd67 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/active_music_session_cleaner.rb @@ -7,7 +7,7 @@ require 'digest/md5' module JamRuby class ActiveMusicSessionCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob attr_accessor :interval @@ -22,11 +22,9 @@ module JamRuby def self.perform @@log.debug("ActiveMusicSessionCleaner waking up") - JamWebEventMachine.run_wait_stop do - cleaner = ActiveMusicSessionCleaner.new - cleaner.interval = "INTERVAL '1 minute'" - cleaner.run - end + cleaner = ActiveMusicSessionCleaner.new + cleaner.interval = "INTERVAL '1 minute'" + cleaner.run @@log.debug("ActiveMusicSessionCleaner done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb b/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb index 6c2d8fe90..782b8a8d9 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/audiomixer_retry.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find jobs that need retrying, and cleanup activities class AudioMixerRetry - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_audiomixer_retry diff --git a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb index 25b643187..c8f67120b 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/cleanup_facebook_signup.rb @@ -2,9 +2,6 @@ module JamRuby class CleanupFacebookSignup - - - @queue = :scheduled_cleanup_facebook_signup @@log = Logging.logger[CleanupFacebookSignup] diff --git a/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb index e5faa2d51..6d233f165 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/daily_session_emailer.rb @@ -1,6 +1,6 @@ module JamRuby class DailySessionEmailer - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_daily_session_emailer @@log = Logging.logger[DailySessionEmailer] diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb index 1e2f6fcc3..7048c9be4 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_config_retry.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find jobs that need retrying class IcecastConfigRetry - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_icecast_config_retry diff --git a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb index b81c5b992..8accb3c43 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/icecast_source_check.rb @@ -9,11 +9,13 @@ module JamRuby # http://blog.bignerdranch.com/1643-never-use-resque-for-serial-jobs/ # periodically scheduled to find sources that need to be brought down, or alternatively, it seems the client failed to start sourcing class IcecastSourceCheck - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_icecast_source_check - @@log = Logging.logger[IcecastSourceCheck] + def log + @log || Logging.logger[IcecastSourceCheck] + end def self.lock_timeout # this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job @@ -21,13 +23,7 @@ module JamRuby end def self.perform - @@log.debug("waking up") - - JamWebEventMachine.run_wait_stop do - IcecastSourceCheck.new.run - end - - @@log.debug("done") + IcecastSourceCheck.new.run end @@ -37,6 +33,8 @@ module JamRuby # ** listeners > 0 and sourced is DOWN (false) # ** listeners == 0 and sourced is UP (true) + log.debug("waking up") + IcecastMount.find_each(lock: true, :conditions => "( (listeners > 0 AND sourced = FALSE) OR (listeners = 0 AND sourced = TRUE) ) AND ( sourced_needs_changing_at IS NULL OR sourced_needs_changing_at < (NOW() - interval '#{APP_CONFIG.icecast_max_sourced_changed} second') ) ", :batch_size => 100) do |mount| if mount.music_session_id mount.with_lock do @@ -44,18 +42,20 @@ module JamRuby end end end + + log.debug("done") end def handle_notifications(mount) if mount.listeners == 0 && mount.sourced # if no listeners, but we are sourced, then ask it to stop sourcing - @@log.debug("SOURCE_DOWN_REQUEST called on mount #{mount.name}") + log.debug("SOURCE_DOWN_REQUEST called on mount #{mount.name}") mount.notify_source_down_requested elsif mount.listeners > 0 && !mount.sourced # if we have some listeners, and still are not sourced, then ask to start sourcing again - @@log.debug("SOURCE_UP_REQUEST called on mount #{mount.name}") + log.debug("SOURCE_UP_REQUEST called on mount #{mount.name}") mount.notify_source_up_requested end diff --git a/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb new file mode 100644 index 000000000..cb14d7866 --- /dev/null +++ b/ruby/lib/jam_ruby/resque/scheduled/jam_tracks_cleaner.rb @@ -0,0 +1,34 @@ +require 'json' +require 'resque' +require 'resque-retry' +require 'net/http' +require 'digest/md5' + +module JamRuby + + # periodically scheduled to find jam_tracks to cleanup + class JamTracksCleaner + extend Resque::Plugins::JamLonelyJob + + @queue = :jam_tracks_cleaner + + class << self + def log + @log || Logging.logger[JamTracksCleaner] + end + + def lock_timeout + # this should be enough time to make sure the job has finished, but not so long that the system isn't recovering from a abandoned job + 1200 + end + + def perform + JamTrackRight.ready_to_clean.each do |jam_track_right| + log.debug("deleting files for jam_track_right #{jam_track_right.id}") + jam_track_right.delete_s3_files + end + end + end + end + +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb b/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb index cd706285a..2ed6b53fc 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/music_session_scheduler.rb @@ -6,7 +6,7 @@ require 'digest/md5' module JamRuby class MusicSessionScheduler - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :music_session_scheduler @@ -19,9 +19,7 @@ module JamRuby def self.perform @@log.debug("MusicSessionScheduler waking up") - JamWebEventMachine.run_wait_stop do - MusicSessionScheduler.new.run - end + MusicSessionScheduler.new.run @@log.debug("MusicSessionScheduler done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb index 2a2c861a4..8012e197a 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/new_musician_emailer.rb @@ -1,6 +1,6 @@ module JamRuby class NewMusicianEmailer - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_new_musician_emailer @@log = Logging.logger[NewMusicianEmailer] diff --git a/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb index 1e4b41769..7903edc0a 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/recordings_cleaner.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find recordings to cleanup class RecordingsCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :recordings_cleaner diff --git a/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb index 8dfeca8e0..8fddeccfb 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/scheduled_music_session_cleaner.rb @@ -1,6 +1,6 @@ module JamRuby class ScheduledMusicSessionCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_music_session_cleaner @@log = Logging.logger[ScheduledMusicSessionCleaner] diff --git a/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb b/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb index 76c759ef8..52819600b 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/score_history_sweeper.rb @@ -8,7 +8,7 @@ module JamRuby # periodically scheduled to find jobs that need retrying class ScoreHistorySweeper - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :score_history_sweeper diff --git a/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb b/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb index d84535b3e..f22dd4bad 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/stats_maker.rb @@ -8,13 +8,25 @@ module JamRuby # creates stats to send to influx periodically class StatsMaker - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob - @queue = :stats_maker + @queue = :scheduled_db_metrics + + def log + @log || Logging.logger[StatsMaker] + end + + def self.lock_timeout + 120 + end - @@log = Logging.logger['StatsMaker'] def self.perform + StatsMaker.new.run + end + + def run + log.debug("starting...") Stats.write('connection', Connection.stats) Stats.write('users', User.stats) end diff --git a/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb b/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb index 9fb3e307f..3d3ca8e76 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/unused_music_notation_cleaner.rb @@ -6,7 +6,7 @@ require 'digest/md5' module JamRuby class UnusedMusicNotationCleaner - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :unused_music_notation_cleaner @@ -20,9 +20,7 @@ module JamRuby def self.perform @@log.debug("waking up") - JamWebEventMachine.run_wait_stop do - UnusedMusicNotationCleaner.new.run - end + UnusedMusicNotationCleaner.new.run @@log.debug("done") end diff --git a/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb b/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb index 99835146f..84e1c5081 100644 --- a/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb +++ b/ruby/lib/jam_ruby/resque/scheduled/user_progress_emailer.rb @@ -1,6 +1,6 @@ module JamRuby class UserProgressEmailer - extend Resque::Plugins::LonelyJob + extend Resque::Plugins::JamLonelyJob @queue = :scheduled_user_progress_emailer @@log = Logging.logger[UserProgressEmailer] diff --git a/ruby/spec/files/on.ogg b/ruby/spec/files/on.ogg new file mode 100644 index 000000000..743d6e3aa Binary files /dev/null and b/ruby/spec/files/on.ogg differ diff --git a/ruby/spec/jam_ruby/models/connection_spec.rb b/ruby/spec/jam_ruby/models/connection_spec.rb index 97784b838..f93e46bf7 100644 --- a/ruby/spec/jam_ruby/models/connection_spec.rb +++ b/ruby/spec/jam_ruby/models/connection_spec.rb @@ -193,5 +193,20 @@ describe JamRuby::Connection do stats['udp_reachable'].should eq(0) stats['networking_testing'].should eq(0) end + + it "1 connection" do + conn.touch + + stats = Connection.stats + stats[Connection::TYPE_CLIENT].should eq(1) + stats[Connection::TYPE_BROWSER].should eq(0) + stats[Connection::TYPE_LATENCY_TESTER].should eq(0) + stats['count'].should eq(1) + stats['scoring_timeout'].should eq(0) + stats['in_session'].should eq(1) + stats['musicians'].should eq(1) + stats['udp_reachable'].should eq(1) + stats['networking_testing'].should eq(0) + end end end diff --git a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb index 5d136d62a..2936fd8d4 100644 --- a/ruby/spec/jam_ruby/models/jam_track_right_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_right_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe JamTrackRight do - + include UsesTempFiles + include CarrierWave::Test::Matchers it "created" do jam_track_right = FactoryGirl.create(:jam_track_right) @@ -16,6 +17,14 @@ describe JamTrackRight do end + it "lists" do + jam_track_right = FactoryGirl.create(:jam_track_right) + jam_tracks = JamTrack.list_downloads(jam_track_right.user) + jam_tracks.should have_key('downloads') + jam_tracks.should have_key('next') + jam_tracks['downloads'].should have(1).items + end + describe "validations" do it "one purchase per user/jam_track combo" do user = FactoryGirl.create(:user) @@ -27,5 +36,92 @@ describe JamTrackRight do right_2.errors[:user_id].should == ['has already been taken'] end end + + describe "JKZ" do + before(:all) do + original_storage = JamTrackTrackUploader.storage = :fog + original_storage = JamTrackRightUploader.storage = :fog + end + + after(:all) do + JamTrackTrackUploader.storage = @original_storage + JamTrackRightUploader.storage = @original_storage + end + + before(:each) do + #content_for_file('abc') + end + + it "should fail if no tracks" do + user = FactoryGirl.create(:user) + jam_track = FactoryGirl.create(:jam_track) + right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + expect { + JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + }.to raise_error(ArgumentError) + end + + it "should create" do + ogg_path = File.join('spec', 'files', 'on.ogg') + user = FactoryGirl.create(:user) + jam_track_track = FactoryGirl.create(:jam_track_track) + jam_track = jam_track_track.jam_track + + uploader = JamTrackTrackUploader.new(jam_track_track, :url) + uploader.store!(File.open(ogg_path, 'rb')) + jam_track_track.save! + + jam_track_track[:url].should == jam_track_track.store_dir + '/' + jam_track_track.filename + + # verify it's on S3 + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track_track[:url]).should be_true + s3.length(jam_track_track[:url]).should == File.size?(ogg_path) + + jam_track_right = JamTrackRight.create(:user=>user, :jam_track=>jam_track) + #expect { + JamRuby::JamTracksManager.save_jam_track_jkz(user, jam_track) + #}.to_not raise_error(ArgumentError) + jam_track_right.reload + jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename + + # verify it's on S3 + url = jam_track_right[:url] + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(url).should be_true + s3.length(url).should > File.size?(ogg_path) + + JamTrackRight.ready_to_clean.count.should == 0 + jam_track_right.destroy + s3.exists?(url).should be_false + end + end + + describe "list_keys" do + let(:user) {FactoryGirl.create(:user)} + + it "empty" do + JamTrackRight.list_keys(user, nil).should eq([]) + end + + it "bogus key" do + JamTrackRight.list_keys(user, ['a']).should eq([]) + end + + it "valid track with no rights to it by querying user" do + jam_track = FactoryGirl.create(:jam_track) + keys = JamTrackRight.list_keys(user, [jam_track.id]) + keys.length.should == 0 + end + + it "valid track with rights to it by querying user" do + jam_track_right = FactoryGirl.create(:jam_track_right, private_key: 'keyabc') + keys = JamTrackRight.list_keys(jam_track_right.user, [jam_track_right.jam_track.id]) + keys.length.should == 1 + keys[0].id.should == jam_track_right.jam_track.id + keys[0]['private_key'].should eq('keyabc') + end + end + end diff --git a/ruby/spec/jam_ruby/models/jam_track_spec.rb b/ruby/spec/jam_ruby/models/jam_track_spec.rb index 85cf9059c..1bddfc5ab 100644 --- a/ruby/spec/jam_ruby/models/jam_track_spec.rb +++ b/ruby/spec/jam_ruby/models/jam_track_spec.rb @@ -97,7 +97,7 @@ describe JamTrack do end describe "upload/download" do - JKA_NAME = 'blah.jka' + JKA_NAME = 'blah.jkz' in_directory_with_file(JKA_NAME) diff --git a/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb b/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb new file mode 100644 index 000000000..36cb5a067 --- /dev/null +++ b/ruby/spec/jam_ruby/resque/jam_tracks_cleaner_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe JamTracksCleaner do + include UsesTempFiles + include CarrierWave::Test::Matchers + RIGHT_NAME = 'abc.jkz' + in_directory_with_file(RIGHT_NAME) + + before (:all) do + @user = FactoryGirl.create(:user) + @jam_track = FactoryGirl.create(:jam_track) + original_storage = JamTrackRightUploader.storage = :fog + end + + after(:all) do + JamTrackRightUploader.storage = @original_storage + end + + + before(:each) do + content_for_file('abc') + end + + it "should clean" do + jam_track_right = JamTrackRight.create(:user=>@user, :jam_track=>@jam_track) + jam_track_right.signed=true + jam_track_right + + jam_track_right.url.store!(File.open(RIGHT_NAME)) + jam_track_right.downloaded_since_sign=true + jam_track_right.save! + + jam_track_right[:url].should == jam_track_right.store_dir + '/' + jam_track_right.filename + jam_track_right.reload + + # Should exist after uploading: + url = jam_track_right[:url] + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + + url.should_not be_nil + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + s3.exists?(jam_track_right[:url]).should be_true + + JamRuby::JamTracksCleaner.perform + s3.exists?(url).should be_true + s3 = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + jam_track_right.update_attribute("updated_at", 6.minutes.ago) + + # But not after running cleaner job: + JamRuby::JamTracksCleaner.perform + s3.exists?(url).should be_false + end +end \ No newline at end of file diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 73e9a3384..890a195c0 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -12,6 +12,10 @@ require 'uses_temp_files' require 'resque_spec' require 'resque_failed_job_mailer' +# to prevent embedded resque code from forking +ENV['FORK_PER_JOB'] = 'false' + + # recreate test database and migrate it SpecDb::recreate_database diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 8dd934198..d2dbb4056 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -74,6 +74,10 @@ def app_config 0 # 0 seconds end + def jamtracks_dir + ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks")) + end + def rabbitmq_host "localhost" end diff --git a/web/README.md b/web/README.md index 03b796614..5aca8b9bf 100644 --- a/web/README.md +++ b/web/README.md @@ -1,4 +1,4 @@ -* TODO +== TODO: Jasmine Javascript Unit Tests ============================= @@ -11,3 +11,4 @@ $ rake jasmine Open browser to localhost:8888 + diff --git a/web/app/assets/javascripts/JamServer.js b/web/app/assets/javascripts/JamServer.js index f60ea07d0..73997c0a7 100644 --- a/web/app/assets/javascripts/JamServer.js +++ b/web/app/assets/javascripts/JamServer.js @@ -321,23 +321,15 @@ lastDisconnectedReason = 'WEBSOCKET_CLOSED_LOCALLY' } - rest.createDiagnostic({ - type: lastDisconnectedReason, - data: {logs: logger.logCache, client_type: clientType, client_id: server.clientID, channel_id: channelId} - }) - .always(function() { - if ($currentDisplay.is('.no-websocket-connection')) { - // this path is the 'not in session path'; so there is nothing else to do - $currentDisplay.removeClass('active'); - - // TODO: tell certain elements that we've reconnected - } - else { - window.location.reload(); - } - }); - + if ($currentDisplay.is('.no-websocket-connection')) { + // this path is the 'not in session path'; so there is nothing else to do + $currentDisplay.removeClass('active'); + // TODO: tell certain elements that we've reconnected + } + else { + window.location.reload(); + } } function buildOptions() { diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index e0988bcf6..a5611ba60 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1344,6 +1344,15 @@ }) } + function clearShoppingCart(options) { + return $.ajax({ + type: "DELETE", + url: '/api/shopping_carts/clear_all', + dataType: "json", + contentType: 'application/json' + }) + } + function getRecurlyAccount() { return $.ajax({ type: "GET", @@ -1374,15 +1383,16 @@ function updateBillingInfo(options) { return $.ajax({ type: "PUT", - url: '/api/recurly/update_billing_info?' + $.param(options), + url: '/api/recurly/update_billing_info?' + $.param({billing_info: options}), dataType: "json", + //data: JSON.stringify({"billing_info": $.param(options)}), contentType: 'application/json' }); } function placeOrder(options) { return $.ajax({ - type: "PUT", + type: "POST", url: '/api/recurly/place_order?' + $.param(options), dataType: "json", contentType: 'application/json' @@ -1534,6 +1544,7 @@ this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; + this.clearShoppingCart = clearShoppingCart; this.getRecurlyAccount = getRecurlyAccount; this.createRecurlyAccount = createRecurlyAccount; this.getBillingInfo = getBillingInfo; diff --git a/web/app/assets/javascripts/jamtrack.js b/web/app/assets/javascripts/jamtrack.js index 6ae767514..a0771762a 100644 --- a/web/app/assets/javascripts/jamtrack.js +++ b/web/app/assets/javascripts/jamtrack.js @@ -156,7 +156,6 @@ e.preventDefault(); var params = {id: $(e.target).attr("data-jamtrack-id")}; - rest.addJamtrackToShoppingCart(params) .done(function(response) { context.location = "/client#/shoppingCart"; diff --git a/web/app/assets/javascripts/jquery.listenbroadcast.js b/web/app/assets/javascripts/jquery.listenbroadcast.js index 349305b57..95c806af2 100644 --- a/web/app/assets/javascripts/jquery.listenbroadcast.js +++ b/web/app/assets/javascripts/jquery.listenbroadcast.js @@ -33,6 +33,7 @@ context.JK.ListenBroadcast = function($parentElement, options){ var WAIT_FOR_BUFFER_TIMEOUT = 5000; + var WAIT_FOR_PLAYING_TIMEOUT = 7000; var RETRY_ATTEMPTS = 5; // we try 4 times, so the user will wait up until RETRY_ATTEMPTS * WAIT_FOR_BUFFER_TIMEOUTS var logger = context.JK.logger; @@ -42,6 +43,7 @@ var audioDomElement = null; var musicSessionId = null; var waitForBufferingTimeout = null; + var waitForPlayingTimeout = null; var fanAccess = null; var audioSrc = null; var audioType = null; @@ -49,6 +51,7 @@ var self = this; var mountInfo = null; var $mountState = null; + var sessionInfo = null; // stored so we can access .mount, mostly var lazyAudioInit = options && options.lazyAudioInit; var hoverOptions = (options && options.hoverOptions) ? options.hoverOptions : {} var $detailHelper = options && options.detailHelper; @@ -77,21 +80,7 @@ e.stopPropagation(); } - if(lazyAudioInit) { - if($audio.length == 0) { - $audio = - $('') - $parent.append($audio) - audioDomElement = $audio.get(0); - audioBind(); - } - } - - if(destroyed) return; - - if(!audioDomElement) throw "no audio element supplied; the user should not be able to attempt a play" + //if(destroyed) return; if(context.JK.ListenBroadcastCurrentlyPlaying) { context.JK.ListenBroadcastCurrentlyPlaying.forcedPause(); @@ -102,20 +91,33 @@ checkServer() .done(function(response) { - if(!response.mount) { + if(!sessionInfo.mount) { transition(PlayStateSessionOver); destroy(); - } + } else { - audioDomElement.play(); + recreateAudioElement(); - retryAttempts = 0; + audioDomElement.load(); - transition(PlayStateInitializing); + retryAttempts = 0; + + transition(PlayStateInitializing); + + // keep this after transition, because any transition clears this timer + waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); + logger.debug("setting buffering timeout"); + rest.addPlayablePlay(musicSessionId, 'JamRuby::MusicSession', null, context.JK.currentUserId); + + if(needsCanPlayGuard()) { + $audio.bind('canplay', function() { + audioDomElement.play(); + }) + } + else { + audioDomElement.play(); + } - // keep this after transition, because any transition clears this timer - waitForBufferingTimeout = setTimeout(noBuffer, WAIT_FOR_BUFFER_TIMEOUT); - rest.addPlayablePlay(musicSessionId, 'JamRuby::MusicSession', null, context.JK.currentUserId); } }) } @@ -131,9 +133,7 @@ e.stopPropagation(); } - if(destroyed) return; - - if(!lazyAudioInit && !audioDomElement) throw "no audio element supplied; the user should not be able to attempt a pause" + //if(destroyed) return; transition(PlayStateNone); @@ -141,12 +141,13 @@ } function destroy() { - if(!destroyed) { - $audio.remove(); - $audio = null; - audioDomElement = null; - destroyed = true; - } + // if(!destroyed) { + //$audio.remove(); + //$audio = null; + //audioDomElement = null; + recreateAudioElement() + // destroyed = true; + //} } function onScreenChanged(e, data) { @@ -155,30 +156,49 @@ } } + function createAudioElementHtml() { + if (sessionInfo == null) throw "no session info"; + if (sessionInfo.mount == null) throw "no session mount info"; + + $audio = + $('') + $parent.append($audio) + audioDomElement = $audio.get(0); + audioBind(); + + } + // this is the only way to make audio stop buffering after the user hits pause function recreateAudioElement() { // jeez: http://stackoverflow.com/questions/4071872/html5-video-force-abort-of-buffering/13302599#13302599 - var originalSource = $audio.html() - audioDomElement.pause(); - audioDomElement.src = ''; - audioDomElement.load(); - var $parent = $audio.parent(); + if(audioDomElement) { + audioDomElement.pause(); + audioDomElement.src = ''; + audioDomElement.load(); + } $audio.remove(); - $parent.append(''); - $audio = $('audio', $parent); - $audio.append(originalSource); - audioDomElement = $audio.get(0); - audioBind(); + createAudioElementHtml(); logger.log("recreated audio element ") } function clearBufferTimeout() { if(waitForBufferingTimeout) { + logger.debug("clearing buffering timeout"); clearTimeout (waitForBufferingTimeout); waitForBufferingTimeout = null; } } + function clearPlayingTimeout() { + if(waitForPlayingTimeout) { + logger.debug("clearing playing timeout"); + clearTimeout (waitForPlayingTimeout); + waitForPlayingTimeout = null; + } + } + function transition(newState) { logger.log("transitioning from " + playState + " to " + newState); @@ -186,8 +206,15 @@ if(newState != PlayStateStalled) { clearBufferTimeout(); + clearPlayingTimeout(); } + if(newState == PlayStateBuffering) { + // give some time after buffering is seen to let play start + waitForPlayingTimeout = setTimeout(noPlay, WAIT_FOR_PLAYING_TIMEOUT) + } + + if( playState == PlayStateNone || playState == PlayStateEnded || playState == PlayStateFailedStart || @@ -202,6 +229,9 @@ triggerStateChange(); } + function noPlay() { + noBuffer(); + } function noBuffer() { waitForBufferingTimeout = null; @@ -218,13 +248,16 @@ checkServer() .done(function(response) { - if(!response.mount) { + if(!sessionInfo.mount) { transition(PlayStateSessionOver); destroy(); } else { // tell audio to stop/start, in attempt to retry //audioDomElement.pause(); + + recreateAudioElement(); + audioDomElement.load(); if(needsCanPlayGuard()) { $audio.bind('canplay', function() { @@ -344,6 +377,10 @@ function checkServer() { return rest.getSession(musicSessionId) + .done(function(response) { + console.log("assigning sessionInfo") + sessionInfo = response; + }) .fail(function(jqXHR) { if(jqXHR.status == 404 || jqXHR.status == 403) { transition(PlayStateSessionOver); @@ -443,7 +480,12 @@ // we have cause to believe the session is done; check against the server if(refresh) { - checkServer(); + checkServer() + .done(function(response) { + if(!sessionInfo.mount) { + transition(PlayStateSessionOver); + destroy(); + }}) } } @@ -634,7 +676,8 @@ function openBubble() { checkServer().done(function(response) { - var mountId = response.mount ? response.mount.id : null + + var mountId = sessionInfo.mount ? sessionInfo.mount.id : null if(mountId) { rest.getMount({id: mountId}) @@ -648,6 +691,10 @@ } else { mountInfo = null; + + transition(PlayStateSessionOver); + destroy(); + context.JK.app.layout.notify('This session can not currently broadcast') } }) @@ -679,8 +726,12 @@ }) } } - function initialize() { + function cacheBustedSrc(src) { + return src + '?cache-buster=' + new Date().getTime(); + } + + function initialize() { musicSessionId = $parent.attr('data-music-session'); if(!musicSessionId) throw "data-music-session must be specified on $parentElement"; @@ -689,13 +740,6 @@ if(fanAccess === null) throw 'fan-access must be specified in $parentElement'; fanAccess = $parent.attr('fan-access') === 'true' // coerce to boolean - if(lazyAudioInit) { - audioSrc = $parent.attr('data-audio-src'); - if(audioSrc === null) throw 'data-audio-src must be specified in $parentElement'; - audioType = $parent.attr('data-audio-type'); - if(audioType === null) throw 'data-audio-type must be specified in $parentElement'; - } - bindHoverDetail(); $audio = $('audio', $parent); @@ -708,11 +752,6 @@ throw "more than one