module JamRuby class Mix < ActiveRecord::Base include S3ManagerMixin MAX_MIX_TIME = 7200 # 2 hours before_destroy :delete_s3_files self.primary_key = 'id' attr_accessible :ogg_url, :should_retry, as: :admin attr_writer :is_skip_mount_uploader belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :mixes, :foreign_key => 'recording_id' mount_uploader :ogg_url, MixUploader before_validation do # this should be an activeadmin only path, because it's using the mount_uploader (whereas the client does something completely different) if ogg_url.present? && ogg_url.respond_to?(:file) && ogg_url_changed? self.ogg_length = ogg_url.file.size self.ogg_md5 = ogg_url.md5 self.completed = true self.started_at = Time.now self.completed_at = Time.now # do not set marking_complete = true; use of marking_complete is a client-centric design, # and setting to true causes client-centric validations end end def self.schedule(recording) raise if recording.nil? mix = Mix.new mix.recording = recording mix.save mix[:ogg_url] = construct_filename(mix.created_at, recording.id, mix.id, type='ogg') mix[:mp3_url] = construct_filename(mix.created_at, recording.id, mix.id, type='mp3') if mix.save mix.enqueue end mix end def enqueue begin Resque.enqueue(AudioMixer, self.id, self.sign_put(3600 * 24, 'ogg'), self.sign_put(3600 * 24, 'mp3')) rescue # implies redis is down. we don't update started_at false end # avoid db validations Mix.where(:id => self.id).update_all(:started_at => Time.now) true end def can_download?(some_user) !ClaimedRecording.find_by_user_id_and_recording_id(some_user.id, recording_id).nil? end def errored(reason, detail) self.error_reason = reason self.error_detail = detail self.error_count = self.error_count + 1 save end def finish(ogg_length, ogg_md5, mp3_length, mp3_md5) self.completed_at = Time.now self.ogg_length = ogg_length self.ogg_md5 = ogg_md5 self.mp3_length = mp3_length self.mp3_md5 = mp3_md5 self.completed = true if save Notification.send_recording_master_mix_complete(recording) end end # valid for 1 day; because the s3 urls eventually expire def manifest one_day = 60 * 60 * 24 manifest = { "files" => [], "timeline" => [] } mix_params = [] recording.recorded_tracks.each do |recorded_track| manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 } mix_params << { "level" => 100, "balance" => 0 } end manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params } manifest["output"] = { "codec" => "vorbis" } manifest["recording_id"] = self.recording.id manifest end def s3_url(type='ogg') if type == 'ogg' s3_manager.s3_url(self[:ogg_url]) else s3_manager.s3_url(self[:mp3_url]) end end def is_completed completed end def sign_url(expiration_time = 120, type='ogg') # expire link in 1 minute--the expectation is that a client is immediately following this link if type == 'ogg' s3_manager.sign_url(self[:ogg_url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) else s3_manager.sign_url(self[:mp3_url], {:expires => expiration_time, :response_content_type => 'audio/mp3', :secure => false}) end end def sign_put(expiration_time = 3600 * 24, type='ogg') if type == 'ogg' s3_manager.sign_url(self[:ogg_url], {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put) else s3_manager.sign_url(self[:mp3_url], {:expires => expiration_time, :content_type => 'audio/mp3', :secure => false}, :put) end end def filename(type='ogg') # construct a path for s3 Mix.construct_filename(self.created_at, self.recording_id, self.id, type) end private def delete_s3_files s3_manager.delete(filename(type='ogg')) if self[:ogg_url] s3_manager.delete(filename(type='mp3')) if self[:mp3_url] end def self.construct_filename(created_at, recording_id, id, type='ogg') raise "unknown ID" unless id "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/mix-#{id}.#{type}" end end end