module JamRuby class Recording < ActiveRecord::Base self.primary_key = 'id' attr_accessible :name, :description, :genre, :is_public, :is_downloadable has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording has_many :users, :through => :recorded_tracks, :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 => :recordings has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id validates :music_session, :presence => true validate :not_already_recording, :on => :create validate :already_stopped_recording def not_already_recording if music_session.is_recording? errors.add(:music_session, ValidationMessages::ALREADY_BEING_RECORDED) end end def already_stopped_recording if is_done && is_done_was errors.add(:music_session, ValidationMessages::NO_LONGER_RECORDING) end end # Start recording a session. def self.start(music_session, owner) recording = nil # Use a transaction and lock to avoid races. music_session.with_lock do recording = Recording.new recording.music_session = music_session recording.owner = owner recording.band = music_session.band if recording.save music_session.connections.each do |connection| connection.tracks.each do |track| recording.recorded_tracks << RecordedTrack.create_from_track(track, recording) end end end 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. music_session = MusicSession.find_by_id(music_session_id) locker = music_session.nil? ? self : music_session locker.with_lock do self.duration = Time.now - created_at self.is_done = true self.save end self 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, description, genre, is_public, is_downloadable) # if self.users.include?(user) # raise PermissionError, "user already claimed this recording" # end unless self.users.exists?(user) raise PermissionError, "user was not in this session" end if self.music_session.is_recording? 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.description = description claimed_recording.genre = genre claimed_recording.is_public = is_public claimed_recording.is_downloadable = is_downloadable self.claimed_recordings << claimed_recording 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.client_track_id, :url => recorded_track.url # FIXME IS THIS RIGHT? } ) end end files end def self.list_downloads(user, limit = 100, since = 0) since = 0 unless since || since == '' # guard against nil 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{ recorded_tracks.id }) .where(%Q{ recorded_tracks.fully_uploaded = TRUE }) .where('recorded_tracks.id > ?', since) .where(:id => user.id).limit(limit).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.client_track_id, :recording_id => recording.id, :length => recorded_track.length, :md5 => recorded_track.md5, :url => recorded_track.filename, :next => recorded_track.id } ) end end end latest_recorded_track = downloads[-1][:next] if downloads.length > 0 # HOW TO LIMIT ON USER User.joins(:recordings).joins(:recordings => :mixes) .order('mixes.id') .where('mixes.completed_at IS NOT NULL') .where('mixes.id > ?', since) .limit(limit).each do |theuser| theuser.recordings.each do |recording| recording.mixes.each do |mix| downloads.push( { :type => "mix", :id => mix.id, :recording_id => recording.id, :length => mix.length, :md5 => mix.md5, :url => mix.filename, :created_at => mix.created_at, :next => mix.id } ) end end end latest_mix = downloads[-1][:next] if downloads.length > 0 if !latest_mix.nil? && !latest_recorded_track.nil? next_date = [latest_mix, latest_recorded_track].max elsif latest_mix.nil? next_date = latest_recorded_track else next_date = latest_mix end if next_date.nil? next_date = since # echo back to the client the same value they passed in, if there are no results end { 'downloads' => downloads, 'next' => next_date.to_s } end def self.list_uploads(user, limit = 100, since = 0) since = 0 unless since || since == '' # guard against nil uploads = [] RecordedTrack .joins(:recording) .where(:user_id => user.id) .where(:fully_uploaded => false) .where('recorded_tracks.id > ?', since) .where("upload_failures <= #{RecordedTrack::MAX_UPLOAD_FAILURES}") .where("duration IS NOT NULL") .order('recorded_tracks.id') .limit(limit).each do |recorded_track| uploads.push({ :type => "recorded_track", :client_track_id => recorded_track.client_track_id, :recording_id => recorded_track.recording_id, :next => recorded_track.id }) end next_value = uploads.length > 0 ? uploads[-1][:next].to_s : nil if next_value.nil? next_value = since # echo back to the client the same value they passed in, if there are no results end { "uploads" => uploads, "next" => next_value.to_s } 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