jam-cloud/ruby/lib/jam_ruby/models/recording.rb

282 lines
9.3 KiB
Ruby

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