module JamRuby # describes what users have rights to which tracks class JamTrackRight < ActiveRecord::Base include JamRuby::S3ManagerMixin attr_accessible :user, :jam_track, :user_id, :jam_track_id, :download_count attr_accessible :user_id, :jam_track_id, as: :admin attr_accessible :url_48, :md5_48, :length_48, :url_44, :md5_44, :length_44 belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track belongs_to :jam_track, class_name: "JamRuby::JamTrack" validates :user, presence:true validates :jam_track, presence:true validates :is_test_purchase, inclusion: {in: [true, false]} validate :verify_download_count after_save :after_save validates_uniqueness_of :user_id, scope: :jam_track_id # Uploads the JKZ: mount_uploader :url_48, JamTrackRightUploader mount_uploader :url_44, JamTrackRightUploader before_destroy :delete_s3_files MAX_JAM_TRACK_DOWNLOADS = 1000 def after_save # try to catch major transitions: # if just queue time changes, start time changes, or signed time changes, send out a notice if signing_queued_at_was != signing_queued_at || signing_started_at_was != signing_started_at || last_signed_at_was != last_signed_at || current_packaging_step != current_packaging_step_was || packaging_steps != packaging_steps_was SubscriptionMessage.jam_track_signing_job_change(self) end end def store_dir "jam_track_rights/#{created_at.strftime('%m-%d-%Y')}/#{user_id}-#{id}" end # create name of the file def filename "#{jam_track.name}.jkz" end def verify_download_count if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}") end end def self.ready_to_clean JamTrackRight.where("downloaded_since_sign=? AND updated_at <= ?", true, 5.minutes.ago).limit(1000) end def finish_errored(error_reason, error_detail) self.last_signed_at = Time.now self.error_count = self.error_count + 1 self.error_reason = error_reason self.error_detail = error_detail self.should_retry = self.error_count < 5 if save Notification.send_jam_track_sign_failed(self) else raise "Error sending notification #{self.errors}" end end def finish_sign(length, md5, bitrate) self.last_signed_at = Time.now if bitrate==48 self.length_48 = length self.md5_48 = md5 else self.length_44 = length self.md5_44 = md5 end self.signed = true self.error_count = 0 self.error_reason = nil self.error_detail = nil self.should_retry = false if save Notification.send_jam_track_sign_complete(self) else raise "Error sending notification #{self.errors}" end end # creates a short-lived URL that has access to the object. # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared def sign_url(expiration_time = 120, bitrate=48) field_name = (bitrate==48) ? "url_48" : "url_44" s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => false}) end def delete_s3_files remove_url_48! remove_url_44! end def enqueue(sample_rate=48) begin JamTrackRight.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil) Resque.enqueue(JamTracksBuilder, self.id, sample_rate) true rescue Exception => e puts "e: #{e}" # implies redis is down. we don't update started_at by bailing out here false end end # if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off def enqueue_if_needed(sample_rate=48) state = signing_state if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED' false else enqueue(sample_rate) true end end # @return true if signed && file exists for the sample_rate specifed: def ready?(sample_rate=48) if sample_rate==48 self.signed && self.url_48.present? && self.url_48.file.exists? else self.signed && self.url_44.present? && self.url_44.file.exists? end end # returns easy to digest state field # SIGNED - the package is ready to be downloaded # ERROR - the package was built unsuccessfully # SIGNING_TIMEOUT - the package was kicked off to be signed, but it seems to have hung # SIGNING - the package is currently signing # QUEUED_TIMEOUT - the package signing job (JamTrackBuilder) was queued, but never executed # QUEUED - the package is queued to sign # QUIET - the jam_track_right exists, but no job has been kicked off; a job needs to be enqueued def signing_state state = nil if signed state = 'SIGNED' elsif signing_started_at # the maximum amount of time the packaging job can take is 10 seconds * num steps. For a 10 track song, this will be 110 seconds. It's a bit long. signing_job_run_max_time = packaging_steps * 10 if Time.now - signing_started_at > signing_job_run_max_time state = 'SIGNING_TIMEOUT' elsif Time.now - last_step_at > APP_CONFIG.signing_step_max_time state = 'SIGNING_TIMEOUT' else state = 'SIGNING' end elsif signing_queued_at if Time.now - signing_queued_at > APP_CONFIG.signing_job_queue_max_time state = 'QUEUED_TIMEOUT' else state = 'QUEUED' end elsif error_count > 0 state = 'ERROR' else state = 'QUIET' # needs to be poked to go build end state end def update_download_count(count=1) self.download_count = self.download_count + count self.last_downloaded_at = Time.now end def self.list_keys(user, jamtracks) if jamtracks.nil? return [] end JamTrack.select('jam_tracks.id, jam_track_rights.private_key AS private_key, jam_track_rights.id AS jam_track_right_id') .joins("LEFT OUTER JOIN jam_track_rights ON jam_tracks.id = jam_track_rights.jam_track_id AND jam_track_rights.user_id = '#{user.id}'") .where('jam_tracks.id IN (?)', jamtracks) end end end