module JamRuby class Recording < ActiveRecord::Base self.primary_key = 'id' has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording has_many :users, :through => :claimed_recordings, :class_name => "JamRuby::User" belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recording has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id # Start recording a session. def self.start(music_session_id, owner) recording = nil # Use a transaction and lock to avoid races. ActiveRecord::Base.transaction do music_session = MusicSession.find(music_session_id, :lock => true) if music_session.nil? raise PermissionError, "the session has ended" end unless music_session.recording.nil? raise PermissionError, "the session is already being recorded" end recording = Recording.new recording.music_session = music_session recording.owner = owner music_session.connections.each do |connection| # Note that we do NOT connect the recording to any users at this point. # That ONLY happens if a user clicks 'save' # recording.users << connection.user connection.tracks.each do |track| RecordedTrack.create_from_track(track, recording) end end # Note that I believe this can be nil. recording.band = music_session.band recording.save music_session.recording = recording music_session.save end # FIXME: # NEED TO SEND NOTIFICATION TO ALL USERS IN THE SESSION THAT RECORDING HAS STARTED HERE. # I'LL STUB IT A BIT. NOTE THAT I REDO THE FIND HERE BECAUSE I DON'T WANT TO SEND THESE # NOTIFICATIONS WHILE THE DB ROW IS LOCKED music_session = MusicSession.find(music_session_id) music_session.connections.each do |connection| # connection.notify_recording_has_started end recording end # Stop recording a session def stop # Use a transaction and lock to avoid races. ActiveRecord::Base.transaction do music_session = MusicSession.find(self.music_session_id, :lock => true) if music_session.nil? raise PermissionError, "the session has ended" end unless music_session.recording raise PermissionError, "the session is not currently being recorded" end music_session.recording = nil music_session.save end self.duration = Time.now - created_at save end # Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording. def claim(user, name, genre, is_public, is_downloadable) if self.users.include?(user) raise PermissionError, "user already claimed this recording" end unless self.recorded_tracks.find { |recorded_track| recorded_track.user == user } raise PermissionError, "user was not in this session" end unless self.music_session.nil? raise PermissionError, "recording cannot be claimed while it is being recorded" end if name.nil? || genre.nil? || is_public.nil? || is_downloadable.nil? raise PermissionError, "recording must have name, genre and flags" end claimed_recording = ClaimedRecording.new claimed_recording.user = user claimed_recording.recording = self claimed_recording.name = name claimed_recording.genre = genre claimed_recording.is_public = is_public claimed_recording.is_downloadable = is_downloadable self.claimed_recordings << claimed_recording save claimed_recording end # Find out if all the tracks for this recording have been uploaded def uploaded? self.recorded_tracks.each do |recorded_track| return false unless recorded_track.fully_uploaded end return true end # Discards this recording and schedules deletion of all files associated with it. def discard self.destroy end # Returns the list of files the user needs to upload. This will only ever be recordings def self.upload_file_list(user) files = [] User.joins(:recordings).joins(:recordings => :recorded_tracks) .where(%Q{ recordings.duration IS NOT NULL }) .where("recorded_tracks.user_id = '#{user.id}'") .where(%Q{ recorded_tracks.fully_uploaded = FALSE }).each do |user| user.recordings.each.do |recording| recording.recorded_tracks.each do |recorded_track| files.push( { :type => "recorded_track", :id => recorded_track.id, :url => recorded_track.url # FIXME IS THIS RIGHT? } ) end end files end # Returns the list of files this user should have synced to his computer, along with md5s and lengths def self.list(user) downloads = [] # That second join is important. It's saying join off of recordings, NOT user. If you take out the # ":recordings =>" part, you'll just get the recorded_tracks that I played. Very different! User.joins(:recordings).joins(:recordings => :recorded_tracks) .order(%Q{ recordings.created_at DESC }) .where(%Q{ recorded_tracks.fully_uploaded = TRUE }) .where(:id => user.id).each do |theuser| theuser.recordings.each do |recording| recording.recorded_tracks.each do |recorded_track| recorded_track = user.claimed_recordings.first.recording.recorded_tracks.first downloads.push( { :type => "recorded_track", :id => recorded_track.id, :length => recorded_track.length, :md5 => recorded_track.md5, :url => recorded_track.url } ) end end end User.joins(:recordings).joins(:recordings => :mixes) .order(%Q{ recordings.created_at DESC }) .where(%Q{ mixes.completed_at IS NOT NULL }).each do |theuser| theuser.recordings.each do |recording| recording.mixes.each do |mix| downloads.push( { :type => "mix", :id => mix.id, :length => mix.length, :md5 => mix.md5, :url => mix.url } ) end end end uploads = [] RecordedTrack .joins(:recording) .where(:user_id => user.id) .where(:fully_uploaded => false) .where("duration IS NOT NULL").each do |recorded_track| uploads.push(recorded_track.filename) end { "downloads" => downloads, "uploads" => uploads } end # Check to see if all files have been uploaded. If so, kick off a mix. def upload_complete # Don't allow multiple mixes for now. raise JamRuby::JamArgumentError unless self.mixes.length == 0 # FIXME: There's a possible race condition here. If two users complete # uploads at the same time, we'll schedule 2 mixes. recorded_tracks.each do |recorded_track| return unless recorded_track.fully_uploaded end self.mixes << Mix.schedule(self, base_mix_manifest.to_json) save end =begin # This is no longer remotely right. def self.search(query, options = { :limit => 10 }) # only issue search if at least 2 characters are specified if query.nil? || query.length < 2 return [] end # create 'anded' statement query = Search.create_tsquery(query) if query.nil? || query.length == 0 return [] end return Recording.where("description_tsv @@ to_tsquery('jamenglish', ?)", query).limit(options[:limit]) end =end def base_mix_manifest manifest = { "files" => [], "timeline" => [] } mix_params = [] recorded_tracks.each do |recorded_track| return nil unless recorded_track.fully_uploaded manifest["files"] << { "url" => recorded_track.url, "codec" => "vorbis", "offset" => 0 } mix_params << { "level" => 100, "balance" => 0 } end manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params } manifest["timeline"] << { "timestamp" => duration, "end" => true } manifest end private def self.validate_user_is_band_member(user, band) unless band.users.exists? user raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR end end def self.validate_user_is_creator(user, creator) unless user.id == creator.id raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR end end def self.validate_user_is_musician(user) unless user.musician? raise PermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR end end end end