2012-11-16 02:08:37 +00:00
|
|
|
module JamRuby
|
|
|
|
|
class Recording < ActiveRecord::Base
|
2013-04-25 06:50:52 +00:00
|
|
|
|
2012-11-16 02:08:37 +00:00
|
|
|
self.primary_key = 'id'
|
|
|
|
|
|
2013-11-03 20:55:55 +00:00
|
|
|
attr_accessible :name, :description, :genre, :is_public, :is_downloadable
|
|
|
|
|
|
2013-04-25 06:50:52 +00:00
|
|
|
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording
|
2013-11-03 20:55:55 +00:00
|
|
|
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
|
2013-01-30 05:46:40 +00:00
|
|
|
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings
|
|
|
|
|
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
|
2013-11-03 20:55:55 +00:00
|
|
|
belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recordings
|
2013-05-06 23:47:20 +00:00
|
|
|
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording
|
2013-01-22 19:15:52 +00:00
|
|
|
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id
|
2013-11-03 20:55:55 +00:00
|
|
|
validates :music_session, :presence => true
|
|
|
|
|
validate :not_already_recording, :on => :create
|
|
|
|
|
validate :already_stopped_recording
|
2013-01-30 05:46:40 +00:00
|
|
|
|
2013-11-03 20:55:55 +00:00
|
|
|
def not_already_recording
|
|
|
|
|
if music_session.is_recording?
|
|
|
|
|
errors.add(:music_session, ValidationMessages::ALREADY_BEING_RECORDED)
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-01-22 19:15:52 +00:00
|
|
|
|
2013-11-03 20:55:55 +00:00
|
|
|
def already_stopped_recording
|
|
|
|
|
if is_done && is_done_was
|
|
|
|
|
errors.add(:music_session, ValidationMessages::NO_LONGER_RECORDING)
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-01-24 01:48:13 +00:00
|
|
|
|
2013-11-03 20:55:55 +00:00
|
|
|
# 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
|
2013-01-24 01:48:13 +00:00
|
|
|
recording = Recording.new
|
|
|
|
|
recording.music_session = music_session
|
2013-01-30 05:46:40 +00:00
|
|
|
recording.owner = owner
|
2013-11-03 20:55:55 +00:00
|
|
|
recording.band = music_session.band
|
2013-12-17 19:44:21 +00:00
|
|
|
recording.id = SecureRandom.uuid # set the recording so that RecordedTrack.create_from_track has an id already on recording
|
2013-11-03 20:55:55 +00:00
|
|
|
|
2013-01-24 01:48:13 +00:00
|
|
|
music_session.connections.each do |connection|
|
|
|
|
|
connection.tracks.each do |track|
|
2013-11-03 20:55:55 +00:00
|
|
|
recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
|
2013-01-24 01:48:13 +00:00
|
|
|
end
|
2013-01-22 19:15:52 +00:00
|
|
|
end
|
2013-01-22 22:03:23 +00:00
|
|
|
|
2013-01-24 01:48:13 +00:00
|
|
|
recording.save
|
2013-01-22 22:03:23 +00:00
|
|
|
end
|
2013-01-30 05:46:40 +00:00
|
|
|
|
2013-01-30 17:57:12 +00:00
|
|
|
|
|
|
|
|
# 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
|
2013-11-03 20:55:55 +00:00
|
|
|
#music_session = MusicSession.find(music_session_id)
|
|
|
|
|
#music_session.connections.each do |connection|
|
|
|
|
|
# # connection.notify_recording_has_started
|
|
|
|
|
#end
|
2013-01-30 17:57:12 +00:00
|
|
|
|
2013-01-30 05:46:40 +00:00
|
|
|
recording
|
2013-01-22 19:15:52 +00:00
|
|
|
end
|
|
|
|
|
|
2013-01-24 01:48:13 +00:00
|
|
|
# Stop recording a session
|
|
|
|
|
def stop
|
|
|
|
|
# Use a transaction and lock to avoid races.
|
2013-11-03 20:55:55 +00:00
|
|
|
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
|
2013-01-24 01:48:13 +00:00
|
|
|
end
|
2013-11-03 20:55:55 +00:00
|
|
|
self
|
2013-01-24 01:48:13 +00:00
|
|
|
end
|
2013-04-25 06:50:52 +00:00
|
|
|
|
|
|
|
|
# Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording.
|
2013-11-03 20:55:55 +00:00
|
|
|
def claim(user, name, description, genre, is_public, is_downloadable)
|
|
|
|
|
# if self.users.include?(user)
|
|
|
|
|
# raise PermissionError, "user already claimed this recording"
|
|
|
|
|
# end
|
2013-04-25 06:50:52 +00:00
|
|
|
|
2013-11-03 20:55:55 +00:00
|
|
|
unless self.users.exists?(user)
|
2013-04-25 06:50:52 +00:00
|
|
|
raise PermissionError, "user was not in this session"
|
|
|
|
|
end
|
|
|
|
|
|
2013-11-03 20:55:55 +00:00
|
|
|
if self.music_session.is_recording?
|
2013-04-25 06:50:52 +00:00
|
|
|
raise PermissionError, "recording cannot be claimed while it is being recorded"
|
2013-02-04 19:12:20 +00:00
|
|
|
end
|
2013-04-25 06:50:52 +00:00
|
|
|
|
|
|
|
|
if name.nil? || genre.nil? || is_public.nil? || is_downloadable.nil?
|
|
|
|
|
raise PermissionError, "recording must have name, genre and flags"
|
2013-01-30 17:57:12 +00:00
|
|
|
end
|
2013-04-25 06:50:52 +00:00
|
|
|
|
|
|
|
|
claimed_recording = ClaimedRecording.new
|
|
|
|
|
claimed_recording.user = user
|
|
|
|
|
claimed_recording.recording = self
|
|
|
|
|
claimed_recording.name = name
|
2013-11-03 20:55:55 +00:00
|
|
|
claimed_recording.description = description
|
2013-04-25 06:50:52 +00:00
|
|
|
claimed_recording.genre = genre
|
|
|
|
|
claimed_recording.is_public = is_public
|
|
|
|
|
claimed_recording.is_downloadable = is_downloadable
|
|
|
|
|
self.claimed_recordings << claimed_recording
|
2013-01-22 19:15:52 +00:00
|
|
|
|
2013-04-25 06:50:52 +00:00
|
|
|
claimed_recording
|
|
|
|
|
end
|
|
|
|
|
|
2013-02-05 02:07:08 +00:00
|
|
|
# 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
|
|
|
|
|
|
2013-04-25 06:50:52 +00:00
|
|
|
# Discards this recording and schedules deletion of all files associated with it.
|
|
|
|
|
def discard
|
|
|
|
|
self.destroy
|
|
|
|
|
end
|
|
|
|
|
|
2013-05-06 23:47:20 +00:00
|
|
|
# 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",
|
2013-12-17 19:44:21 +00:00
|
|
|
:id => recorded_track.client_track_id,
|
2013-05-06 23:47:20 +00:00
|
|
|
:url => recorded_track.url # FIXME IS THIS RIGHT?
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
files
|
|
|
|
|
end
|
|
|
|
|
|
2013-12-05 11:16:21 +00:00
|
|
|
def self.list_downloads(user, limit = 100, since = Date.new(1))
|
|
|
|
|
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 })
|
|
|
|
|
.where(%Q{ recorded_tracks.fully_uploaded = TRUE })
|
|
|
|
|
.where('recorded_tracks.created_at > ?', 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",
|
2013-12-17 19:44:21 +00:00
|
|
|
:id => recorded_track.client_track_id,
|
2013-12-05 11:16:21 +00:00
|
|
|
:recording_id => recording.id,
|
|
|
|
|
:length => recorded_track.length,
|
|
|
|
|
:md5 => recorded_track.md5,
|
2013-12-17 19:44:21 +00:00
|
|
|
:url => recorded_track.filename,
|
2013-12-05 11:16:21 +00:00
|
|
|
:created_at => recorded_track.created_at
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
latest_recorded_track = downloads[-1][:created_at] if downloads.length > 0
|
|
|
|
|
|
|
|
|
|
User.joins(:recordings).joins(:recordings => :mixes)
|
|
|
|
|
.order('recordings.created_at')
|
|
|
|
|
.where('mixes.completed_at IS NOT NULL')
|
|
|
|
|
.where('mixes.created_at > ?', 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,
|
2013-12-17 19:44:21 +00:00
|
|
|
:url => mix.filename,
|
2013-12-05 11:16:21 +00:00
|
|
|
:created_at => mix.created_at
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
latest_mix = downloads[-1][:created_at] 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
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
'downloads' => downloads,
|
|
|
|
|
'next' => next_date
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def self.list_uploads(user, limit = 100, since = Date.new(1))
|
|
|
|
|
uploads = []
|
|
|
|
|
RecordedTrack
|
|
|
|
|
.joins(:recording)
|
|
|
|
|
.where(:user_id => user.id)
|
|
|
|
|
.where(:fully_uploaded => false)
|
2013-12-17 19:44:21 +00:00
|
|
|
.where("upload_failures <= #{RecordedTrack::MAX_UPLOAD_FAILURES}")
|
2013-12-05 11:16:21 +00:00
|
|
|
.where("duration IS NOT NULL").each do |recorded_track|
|
|
|
|
|
uploads.push({
|
|
|
|
|
:type => "recorded_track",
|
2013-12-17 19:44:21 +00:00
|
|
|
:client_track_id => recorded_track.client_track_id,
|
2013-12-05 11:16:21 +00:00
|
|
|
:recording_id => recorded_track.recording_id,
|
|
|
|
|
:created_at => recorded_track.created_at
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
"uploads" => uploads,
|
|
|
|
|
"next" => uploads.length > 0 ? uploads[-1][:created_at] : nil
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
2013-05-06 23:47:20 +00:00
|
|
|
# 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
|
|
|
|
|
|
2013-04-25 06:50:52 +00:00
|
|
|
=begin
|
|
|
|
|
# This is no longer remotely right.
|
2013-01-14 04:50:38 +00:00
|
|
|
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
|
2013-01-30 05:46:40 +00:00
|
|
|
=end
|
2012-11-21 19:48:39 +00:00
|
|
|
|
2013-06-30 20:55:03 +00:00
|
|
|
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 }
|
2013-05-06 23:47:20 +00:00
|
|
|
end
|
2013-06-30 20:55:03 +00:00
|
|
|
|
|
|
|
|
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
|
|
|
|
|
manifest["timeline"] << { "timestamp" => duration, "end" => true }
|
|
|
|
|
manifest
|
|
|
|
|
end
|
2013-04-25 06:50:52 +00:00
|
|
|
|
2013-06-30 20:55:03 +00:00
|
|
|
private
|
|
|
|
|
def self.validate_user_is_band_member(user, band)
|
|
|
|
|
unless band.users.exists? user
|
|
|
|
|
raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
2012-11-22 08:27:23 +00:00
|
|
|
end
|
2013-06-30 20:55:03 +00:00
|
|
|
end
|
2012-11-22 08:27:23 +00:00
|
|
|
|
2013-06-30 20:55:03 +00:00
|
|
|
def self.validate_user_is_creator(user, creator)
|
|
|
|
|
unless user.id == creator.id
|
|
|
|
|
raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
2012-11-22 08:27:23 +00:00
|
|
|
end
|
2013-06-30 20:55:03 +00:00
|
|
|
end
|
2012-11-22 08:27:23 +00:00
|
|
|
|
2013-06-30 20:55:03 +00:00
|
|
|
def self.validate_user_is_musician(user)
|
|
|
|
|
unless user.musician?
|
|
|
|
|
raise PermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR
|
2012-11-22 08:27:23 +00:00
|
|
|
end
|
2013-06-30 20:55:03 +00:00
|
|
|
end
|
2012-11-22 08:27:23 +00:00
|
|
|
|
2013-05-06 23:47:20 +00:00
|
|
|
end
|
2013-04-25 06:50:52 +00:00
|
|
|
end
|