743 lines
27 KiB
Ruby
743 lines
27 KiB
Ruby
module JamRuby
|
|
class Recording < ActiveRecord::Base
|
|
|
|
@@log = Logging.logger[Recording]
|
|
|
|
attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, :jam_track_id, as: :admin
|
|
|
|
has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
|
|
has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
|
|
has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
|
|
has_many :quick_mixes, :class_name => "JamRuby::QuickMix", :foreign_key => :recording_id, :dependent => :destroy
|
|
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
|
|
has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy
|
|
has_many :recorded_backing_tracks, :class_name => "JamRuby::RecordedBackingTrack", :foreign_key => :recording_id, :dependent => :destroy
|
|
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :recording_id, :dependent => :destroy
|
|
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
|
|
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
|
|
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
|
|
has_one :feed, :class_name => "JamRuby::Feed", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
|
|
|
|
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings, :foreign_key => 'owner_id'
|
|
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
|
|
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :recordings, foreign_key: :music_session_id
|
|
belongs_to :non_active_music_session, :class_name => "JamRuby::MusicSession", foreign_key: :music_session_id
|
|
belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :inverse_of => :recordings, :foreign_key => 'jam_track_id'
|
|
belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id'
|
|
|
|
accepts_nested_attributes_for :recorded_tracks, :mixes, :claimed_recordings, allow_destroy: true
|
|
|
|
validate :not_already_recording, :on => :create
|
|
validate :not_still_finalizing_previous, :on => :create
|
|
validate :not_playback_recording, :on => :create
|
|
validate :already_stopped_recording
|
|
validate :only_one_mix
|
|
|
|
before_save :sanitize_active_admin
|
|
before_create :add_to_feed
|
|
|
|
def add_to_feed
|
|
feed = Feed.new
|
|
feed.recording = self
|
|
end
|
|
|
|
def sanitize_active_admin
|
|
self.owner_id = nil if self.owner_id == ''
|
|
self.band_id = nil if self.band_id == ''
|
|
end
|
|
|
|
def comment_count
|
|
self.comments.size
|
|
end
|
|
|
|
def is_jamtrack_recording?
|
|
!jam_track_id.nil? && parsed_timeline['jam_track_isplaying']
|
|
end
|
|
|
|
def parsed_timeline
|
|
timeline ? JSON.parse(timeline) : {}
|
|
end
|
|
|
|
def high_quality_mix?
|
|
has_final_mix
|
|
end
|
|
|
|
def has_mix?
|
|
# this is used by the UI to know whether it can show a play button. We prefer a real mix, but a stream mix will do
|
|
return true if high_quality_mix?
|
|
has_stream_mix
|
|
end
|
|
|
|
# this should be a has-one relationship. until this, this is easiest way to get from recording > mix
|
|
def mix
|
|
self.mixes[0] if self.mixes.length > 0
|
|
end
|
|
|
|
def mix_state
|
|
mix.state if mix
|
|
end
|
|
|
|
def mix_error
|
|
mix.error if mix
|
|
end
|
|
|
|
def stream_mix
|
|
quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
|
|
end
|
|
|
|
def mix_state
|
|
mix.state if mix
|
|
end
|
|
|
|
def mix_error
|
|
mix.error if mix
|
|
end
|
|
|
|
def stream_mix
|
|
quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
|
|
end
|
|
|
|
# this can probably be done more efficiently, but David needs this asap for a video
|
|
def grouped_tracks
|
|
tracks = []
|
|
sorted_tracks = self.recorded_tracks.sort { |a,b| a.user.id <=> b.user.id }
|
|
|
|
t = Track.new
|
|
t.instrument_ids = []
|
|
sorted_tracks.each_with_index do |track, index|
|
|
if index > 0
|
|
if sorted_tracks[index-1].user.id != sorted_tracks[index].user.id
|
|
t = Track.new
|
|
t.instrument_ids = []
|
|
t.instrument_ids << track.instrument.id
|
|
t.musician = track.user
|
|
tracks << t
|
|
else
|
|
if !t.instrument_ids.include? track.instrument.id
|
|
t.instrument_ids << track.instrument.id
|
|
end
|
|
end
|
|
else
|
|
t.musician = track.user
|
|
t.instrument_ids << track.instrument.id
|
|
tracks << t
|
|
end
|
|
end
|
|
tracks
|
|
end
|
|
|
|
def not_already_recording
|
|
if music_session && music_session.is_recording?
|
|
errors.add(:music_session, ValidationMessages::ALREADY_BEING_RECORDED)
|
|
end
|
|
end
|
|
|
|
# this should be used to cleanup a recording that we detect is no longer running
|
|
def abort
|
|
recording.music_session.claimed_recording_id = nil
|
|
recording.music_session.claimed_recording_initiator_id = nil
|
|
|
|
# double check that there are no claims to this recording before destroying it
|
|
unless claimed_recordings.length > 0
|
|
destroy
|
|
end
|
|
end
|
|
|
|
def not_still_finalizing_previous
|
|
# after a recording is done, users need to keep or discard it.
|
|
# this checks if the previous recording is still being finalized
|
|
|
|
unless !music_session || music_session.is_recording?
|
|
previous_recording = music_session.most_recent_recording
|
|
if previous_recording
|
|
previous_recording.recorded_tracks.each do |recorded_track|
|
|
# if at least one user hasn't taken any action yet...
|
|
if recorded_track.discard.nil?
|
|
# and they are still around and in this music session still...
|
|
connection = Connection.find_by_client_id(recorded_track.client_id)
|
|
if !connection.nil? && connection.music_session == music_session
|
|
errors.add(:music_session, ValidationMessages::PREVIOUS_RECORDING_STILL_BEING_FINALIZED)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def not_playback_recording
|
|
if music_session && music_session.is_playing_recording?
|
|
errors.add(:music_session, ValidationMessages::ALREADY_PLAYBACK_RECORDING)
|
|
end
|
|
end
|
|
|
|
def already_stopped_recording
|
|
if is_done && is_done_was
|
|
errors.add(:music_session, ValidationMessages::NO_LONGER_RECORDING)
|
|
end
|
|
end
|
|
|
|
|
|
def only_one_mix
|
|
# we leave mixes as has_many because VRFS-1089 was very hard to do with has_one + cocoon add/remove
|
|
if mixes.length > 1
|
|
errors.add(:mixes, ValidationMessages::ONLY_ONE_MIX)
|
|
end
|
|
end
|
|
|
|
def recorded_tracks_for_user(user)
|
|
unless self.users.exists?(user.id)
|
|
raise JamPermissionError, "user was not in this session"
|
|
end
|
|
recorded_tracks.where(:user_id => user.id)
|
|
end
|
|
|
|
def recorded_backing_tracks_for_user(user)
|
|
unless self.users.exists?(user.id)
|
|
raise JamPermissionError, "user was not in this session"
|
|
end
|
|
recorded_backing_tracks.where(:user_id => user.id)
|
|
end
|
|
|
|
|
|
def has_access?(user)
|
|
users.exists?(user.id) || plays.where("player_id=?", user).count != 0
|
|
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
|
|
GoogleAnalyticsEvent.report_band_recording(recording.band)
|
|
|
|
# make quick mixes *before* the audio/video tracks, because this will give them precedence in list_uploads
|
|
music_session.users.uniq.each do |user|
|
|
QuickMix.create(recording, user)
|
|
end
|
|
|
|
music_session.connections.each do |connection|
|
|
connection.tracks.each do |track|
|
|
recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
|
|
end
|
|
|
|
connection.video_sources.each do |video|
|
|
recording.recorded_videos << RecordedVideo.create_from_video_source(video, recording)
|
|
end
|
|
|
|
connection.backing_tracks.each do |backing_track|
|
|
recording.recorded_backing_tracks << RecordedBackingTrack.create_from_backing_track(backing_track, recording)
|
|
end
|
|
end
|
|
|
|
if music_session.jam_track
|
|
music_session.jam_track.jam_track_tracks.each do |jam_track_track|
|
|
recording.recorded_jam_track_tracks << RecordedJamTrackTrack.create_from_jam_track_track(jam_track_track, recording, owner) if jam_track_track.track_type == 'Track'
|
|
end
|
|
recording.jam_track = music_session.jam_track
|
|
recording.jam_track_initiator = music_session.jam_track_initiator
|
|
end
|
|
|
|
recording.save
|
|
end
|
|
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, upload_to_youtube=false)
|
|
upload_to_youtube = !!upload_to_youtube # Correct where nil is borking save
|
|
unless self.users.exists?(user.id)
|
|
raise JamPermissionError, "user was not in this session"
|
|
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.upload_to_youtube = upload_to_youtube
|
|
self.claimed_recordings << claimed_recording
|
|
|
|
if claimed_recording.save
|
|
keep(user)
|
|
end
|
|
|
|
claimed_recording
|
|
end
|
|
|
|
# the user votes to keep their tracks for this recording
|
|
def keep(user)
|
|
recorded_tracks_for_user(user).update_all(:discard => false)
|
|
|
|
Recording.where(:id => id).update_all(:updated_at => Time.now) # updated updated_at for benefit of RecordingsCleaner
|
|
|
|
User.where(:id => user.id).update_all(:first_recording_at => Time.now ) unless user.first_recording_at
|
|
end
|
|
|
|
|
|
# the user votes to discard their tracks for this recording
|
|
def discard(user)
|
|
recorded_tracks_for_user(user).update_all(:discard => true)
|
|
|
|
Recording.where(:id => id).update_all(:updated_at => Time.now) # updated updated_at for benefit of RecordingsCleaner
|
|
|
|
# check if all recorded_tracks for this recording are discarded
|
|
if recorded_tracks.where('discard = false or discard is NULL').length == 0
|
|
self.all_discarded = true # the feed won't pick this up; also background cleanup will find these and whack them later
|
|
self.save(:validate => false)
|
|
end
|
|
end
|
|
|
|
# only discard if the user has previously taken no action
|
|
def discard_if_no_action(user)
|
|
track = recorded_tracks_for_user(user).first
|
|
if track.discard.nil?
|
|
discard(user )
|
|
end
|
|
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
|
|
|
|
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!
|
|
|
|
# we also only allow you to be told about downloads if you have claimed the recording
|
|
#User.joins(:recordings).joins(:recordings => :recorded_tracks).joins(:recordings => :claimed_recordings)
|
|
RecordedTrack.joins(:recording).joins(:recording => :claimed_recordings)
|
|
.order('recorded_tracks.id')
|
|
.where('recorded_tracks.fully_uploaded = TRUE')
|
|
.where('recorded_tracks.id > ?', since)
|
|
.where('all_discarded = false')
|
|
.where('deleted = false')
|
|
.where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_track|
|
|
downloads.push(
|
|
{
|
|
:type => "recorded_track",
|
|
:id => recorded_track.client_track_id,
|
|
:recording_id => recorded_track.recording_id,
|
|
:length => recorded_track.length,
|
|
:md5 => recorded_track.md5,
|
|
:url => recorded_track[:url],
|
|
:next => recorded_track.id
|
|
}
|
|
)
|
|
end
|
|
latest_recorded_track = (downloads.length > 0) ? downloads[-1][:next] : 0
|
|
|
|
Mix.joins(:recording).joins(:recording => :claimed_recordings)
|
|
.order('mixes.id')
|
|
.where('mixes.completed_at IS NOT NULL')
|
|
.where('mixes.id > ?', since)
|
|
.where('all_discarded = false')
|
|
.where('deleted = false')
|
|
.where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user)
|
|
.limit(limit).each do |mix|
|
|
downloads.push(
|
|
{
|
|
:type => "mix",
|
|
:id => mix.id.to_s,
|
|
:recording_id => mix.recording_id,
|
|
:length => mix.ogg_length,
|
|
:md5 => mix.ogg_md5,
|
|
:url => mix.ogg_url,
|
|
:created_at => mix.created_at,
|
|
:next => mix.id
|
|
}
|
|
)
|
|
end
|
|
latest_mix = (downloads.length > 0) ? downloads[-1][:next] : 0
|
|
|
|
RecordedBackingTrack.joins(:recording).joins(:recording => :claimed_recordings)
|
|
.order('recorded_backing_tracks.id')
|
|
.where('recorded_backing_tracks.fully_uploaded = TRUE')
|
|
.where('recorded_backing_tracks.id > ?', since)
|
|
.where('recorded_backing_tracks.user_id = ?', user.id) # only the person who opened the backing track can have it back
|
|
.where('all_discarded = false')
|
|
.where('deleted = false')
|
|
.where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_backing_track|
|
|
downloads.push(
|
|
{
|
|
:type => "recorded_backing_track",
|
|
:id => recorded_backing_track.client_track_id,
|
|
:recording_id => recorded_backing_track.recording_id,
|
|
:length => recorded_backing_track.length,
|
|
:md5 => recorded_backing_track.md5,
|
|
:url => recorded_backing_track[:url],
|
|
:next => recorded_backing_track.id
|
|
}
|
|
)
|
|
end
|
|
latest_recorded_backing_track = (downloads.length > 0) ? downloads[-1][:next] : 0
|
|
|
|
next_date = [latest_mix, latest_recorded_track, latest_recorded_backing_track].max
|
|
|
|
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 = []
|
|
|
|
# Uploads now include videos in addition to the tracks.
|
|
# This is accomplished using a SQL UNION query via arel, as follows:
|
|
|
|
# Select fields from track. Note the reorder, which removes
|
|
# the default scope sort as it b0rks the union. Also note the
|
|
# alias so that we can differentiate tracks and videos when
|
|
# processing the results:
|
|
track_arel = RecordedTrack.select([
|
|
:id,
|
|
:recording_id,
|
|
:user_id,
|
|
:url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
:client_track_id,
|
|
Arel::Nodes::As.new(Arel::Nodes.build_quoted('track'), Arel.sql('item_type'))
|
|
]).reorder("")
|
|
|
|
# Select fields for video. Note that it must include
|
|
# the same number of fields as the track in order for
|
|
# the union to work:
|
|
vid_arel = RecordedVideo.select([
|
|
:id,
|
|
:recording_id,
|
|
:user_id,
|
|
:url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
:client_video_source_id,
|
|
Arel::Nodes::As.new(Arel::Nodes.build_quoted('video'), Arel.sql('item_type'))
|
|
]).reorder("")
|
|
|
|
# Select fields for quick mix. Note that it must include
|
|
# the same number of fields as the track or video in order for
|
|
# the union to work:
|
|
quick_mix_arel = QuickMix.select([
|
|
:id,
|
|
:recording_id,
|
|
:user_id,
|
|
:ogg_url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
Arel::Nodes::As.new(Arel::Nodes.build_quoted(''), Arel.sql('quick_mix_track_id')),
|
|
Arel::Nodes::As.new(Arel::Nodes.build_quoted('stream_mix'), Arel.sql('item_type'))
|
|
]).reorder("")
|
|
|
|
# Select fields for quick mix. Note that it must include
|
|
# the same number of fields as the track or video in order for
|
|
# the union to work:
|
|
backing_track_arel = RecordedBackingTrack.select([
|
|
:id,
|
|
:recording_id,
|
|
:user_id,
|
|
:url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
:client_track_id,
|
|
Arel::Nodes::As.new(Arel::Nodes.build_quoted('backing_track'), Arel.sql('item_type'))
|
|
]).reorder("")
|
|
|
|
# Glue them together:
|
|
union = track_arel.union(vid_arel)
|
|
|
|
utable = Arel::Nodes::TableAlias.new(union, :recorded_items)
|
|
arel = track_arel.from(utable)
|
|
arel = arel.except(:select)
|
|
|
|
# Remove the implicit select created by .from. It
|
|
# contains an ambigious "id" field:
|
|
arel = arel.except(:select)
|
|
arel = arel.select([
|
|
"recorded_items.id",
|
|
:recording_id,
|
|
:user_id,
|
|
:url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
:client_track_id,
|
|
:item_type
|
|
])
|
|
|
|
# And repeat:
|
|
union_quick = arel.union(quick_mix_arel)
|
|
utable_quick = Arel::Nodes::TableAlias.new(union_quick, :recorded_items_quick)
|
|
arel = arel.from(utable_quick)
|
|
|
|
arel = arel.except(:select)
|
|
arel = arel.select([
|
|
"recorded_items_quick.id",
|
|
:recording_id,
|
|
:user_id,
|
|
:url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
:client_track_id,
|
|
:item_type
|
|
])
|
|
|
|
|
|
# And repeat for backing track:
|
|
union_all = arel.union(backing_track_arel)
|
|
utable_all = Arel::Nodes::TableAlias.new(union_all, :recorded_items_all)
|
|
arel = arel.from(utable_all)
|
|
|
|
arel = arel.except(:select)
|
|
arel = arel.select([
|
|
"recorded_items_all.id",
|
|
:recording_id,
|
|
:user_id,
|
|
:url,
|
|
:fully_uploaded,
|
|
:upload_failures,
|
|
:client_track_id,
|
|
:item_type
|
|
])
|
|
|
|
# Further joining and criteria for the unioned object:
|
|
arel = arel.joins("INNER JOIN recordings ON recordings.id=recorded_items_all.recording_id") \
|
|
.where('recorded_items_all.user_id' => user.id) \
|
|
.where('recorded_items_all.fully_uploaded = ?', false) \
|
|
.where('recorded_items_all.id > ?', since) \
|
|
.where("upload_failures <= #{APP_CONFIG.max_track_upload_failures}") \
|
|
.where("duration IS NOT NULL") \
|
|
.where('all_discarded = false') \
|
|
.where('deleted = false') \
|
|
.order('recorded_items_all.id') \
|
|
.limit(limit)
|
|
|
|
# Load into array:
|
|
arel.each do |recorded_item|
|
|
if recorded_item.item_type=='video'
|
|
# A video:
|
|
uploads << ({
|
|
:type => "recorded_video",
|
|
:client_video_source_id => recorded_item.client_track_id,
|
|
:recording_id => recorded_item.recording_id,
|
|
:next => recorded_item.id
|
|
})
|
|
elsif recorded_item.item_type == 'track'
|
|
# A track:
|
|
uploads << ({
|
|
:type => "recorded_track",
|
|
:client_track_id => recorded_item.client_track_id,
|
|
:recording_id => recorded_item.recording_id,
|
|
:next => recorded_item.id
|
|
})
|
|
elsif recorded_item.item_type == 'stream_mix'
|
|
uploads << ({
|
|
:type => "stream_mix",
|
|
:recording_id => recorded_item.recording_id,
|
|
:next => recorded_item.id
|
|
})
|
|
elsif recorded_item.item_type == 'backing_track'
|
|
uploads << ({
|
|
:type => "recorded_backing_track",
|
|
:recording_id => recorded_item.recording_id,
|
|
:client_track_id => recorded_item.client_track_id,
|
|
:next => recorded_item.id
|
|
})
|
|
else
|
|
|
|
end
|
|
|
|
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
|
|
|
|
def preconditions_for_mix?
|
|
recorded_tracks.each do |recorded_track|
|
|
return false unless recorded_track.fully_uploaded
|
|
end
|
|
|
|
recorded_backing_tracks.each do |recorded_backing_track|
|
|
return false unless recorded_backing_track.fully_uploaded
|
|
end
|
|
|
|
true
|
|
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.
|
|
return if 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.
|
|
|
|
self.mixes << Mix.schedule(self) if preconditions_for_mix?
|
|
|
|
save
|
|
end
|
|
|
|
def is_public?
|
|
claimed_recordings.where(is_public: true).length > 0
|
|
end
|
|
|
|
# meant to be used as a way to 'pluck' a claimed_recording appropriate for user.
|
|
def candidate_claimed_recording
|
|
#claimed_recordings.where(is_public: true).first
|
|
claimed_recordings.first
|
|
end
|
|
|
|
# returns a ClaimedRecording that the user did not discard
|
|
def claim_for_user(user, ignore_discarded = false)
|
|
return nil unless user
|
|
claim = claimed_recordings.find{|claimed_recording| claimed_recording.user == user }
|
|
if ignore_discarded
|
|
claim
|
|
else
|
|
claim unless claim && claim.discarded
|
|
end
|
|
end
|
|
|
|
def self.when_will_be_discarded?
|
|
|
|
recorded_track_votes = recorded_tracks.map(&:discard)
|
|
|
|
discarded = 0
|
|
recorded_track_votes.each do |discard_vote|
|
|
if discard_vote == nil || discard_vote == true
|
|
discarded = discarded + 1
|
|
end
|
|
end
|
|
|
|
if recorded_track_votes.length == discarded
|
|
# all tracks are discarded, figure out due time for deletion
|
|
# 3 days in seconds - amount of seconds since last updated
|
|
((APP_CONFIG.recordings_stale_time * 3600 * 24) - (Time.now - updated_at).to_i).seconds.from_now
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
# finds all discarded recordings that are sufficiently stale (i.e., abandoned by all those involved, and hasn't been mucked with in a while)
|
|
def self.discarded_and_stale
|
|
|
|
# we count up all tracks for the Recording, and count up all discarded/not-voted-on tracks
|
|
# if they are equal, and if the recording is stale, let's return it.
|
|
Recording
|
|
.joins("INNER JOIN recorded_tracks ON recordings.id = recorded_tracks.recording_id")
|
|
.joins(%Q{
|
|
LEFT OUTER JOIN
|
|
(SELECT id
|
|
FROM recorded_tracks WHERE discard IS NULL OR discard = TRUE) AS discard_info
|
|
ON recorded_tracks.id = discard_info.id
|
|
})
|
|
.group("recordings.id")
|
|
.having('COUNT(recorded_tracks.id) = COUNT(discard_info.id)')
|
|
.where("NOW() - recordings.updated_at > '#{APP_CONFIG.recordings_stale_time} day'::INTERVAL")
|
|
.limit(1000)
|
|
.readonly(false)
|
|
end
|
|
|
|
def mark_delete
|
|
|
|
mixes.each do |mix|
|
|
mix.delete_s3_files
|
|
end
|
|
|
|
quick_mixes.each do |quick_mix|
|
|
quick_mix.delete_s3_files
|
|
end
|
|
|
|
recorded_tracks.each do |recorded_track|
|
|
recorded_track.delete_s3_files
|
|
end
|
|
|
|
self.deleted = true
|
|
self.save(:validate => false)
|
|
end
|
|
|
|
def add_timeline(timeline)
|
|
global = timeline["global"]
|
|
raise JamArgumentError, "global must be specified" unless global
|
|
|
|
tracks = timeline["tracks"]
|
|
raise JamArgumentError, "tracks must be specified" unless tracks
|
|
|
|
Recording.where(id: self.id).update_all(timeline: global)
|
|
|
|
jam_tracks = tracks.select {|track| track["type"] == "jam_track"}
|
|
jam_tracks.each do |client_jam_track|
|
|
RecordedJamTrackTrack.where(recording_id: id, jam_track_track_id: client_jam_track["id"]).update_all(timeline: client_jam_track["timeline"])
|
|
end
|
|
end
|
|
|
|
def self.popular_recordings(limit = 100)
|
|
Recording.select('recordings.id').joins('inner join claimed_recordings ON claimed_recordings.recording_id = recordings.id AND claimed_recordings.is_public = TRUE').where(all_discarded: false).where(is_done: true).where(deleted: false).order('play_count DESC').limit(limit).group('recordings.id')
|
|
end
|
|
|
|
private
|
|
def self.validate_user_is_band_member(user, band)
|
|
unless band.users.exists? user.id
|
|
raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
|
|
end
|
|
end
|
|
|
|
def self.validate_user_is_creator(user, creator)
|
|
unless user.id == creator.id
|
|
raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
|
|
end
|
|
end
|
|
|
|
def self.validate_user_is_musician(user)
|
|
unless user.musician?
|
|
raise JamPermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|