<commit_before># frozen_string_literal: true

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

require 'csv'

class MediaObject < ActiveRecord::Base
  include Workflow
  include SearchTermHelper
  belongs_to :user
  belongs_to :context, polymorphic:
    [:course, :group, :conversation_message, :account, :assignment,
     :assessment_question, { context_user: 'User' }], exhaustive: false
  belongs_to :attachment
  belongs_to :root_account, :class_name => 'Account'

  validates_presence_of :media_id, :workflow_state
  has_many :media_tracks, -> { order(:locale) }, dependent: :destroy
  after_create :retrieve_details_later
  after_save :update_title_on_kaltura_later
  serialize :data

  attr_accessor :podcast_associated_asset

  def user_entered_title=(val)
    @push_user_title = true
    write_attribute(:user_entered_title, val)
  end

  def update_title_on_kaltura_later
    delay.update_title_on_kaltura if @push_user_title
    @push_user_title = nil
  end

  def self.find_by_media_id(media_id)
    unless Rails.env.production?
      raise "Do not look up MediaObjects by media_id - use the scope by_media_id instead to support migrated content."
    end
    super
  end


  set_policy do
    given { |user| (self.user && self.user == user) || (self.context && self.context.grants_right?(user, :manage_content)) }
    can :add_captions and can :delete_captions
  end

  # if wait_for_completion is true, this will wait SYNCHRONOUSLY for the bulk
  # upload to complete. Wrap it in a timeout if you ever want it to give up
  # waiting.
  def self.add_media_files(attachments, wait_for_completion)
    KalturaMediaFileHandler.new.add_media_files(attachments, wait_for_completion)
  end

  def self.bulk_migration(csv, root_account_id)
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    res = client.bulkUploadCsv(csv)
    if !res[:ready]
      MediaObject.delay(run_at: 1.minute.from_now, priority: Delayed::LOW_PRIORITY).
        refresh_media_files(res[:id], [], root_account_id)
    else
      build_media_objects(res, root_account_id)
    end
    res
  end

  def self.migration_csv(media_objects)
    CSV.generate do |csv|
      media_objects.each do |mo|
        mo.retrieve_details unless mo.data[:download_url]
        if mo.data[:download_url]
          row = []
          row << mo.title
          row << ""
          row << "old_id_#{mo.media_id}"
          row << mo.data[:download_url]
          row << mo.media_type.capitalize
          csv << row
        end
      end
    end
  end

  def self.build_media_objects(data, root_account_id)
    root_account = Account.where(id: root_account_id).first
    data[:entries].each do |entry|
      attachment_id = nil
      if entry[:originalId].present? && (Integer(entry[:originalId]).is_a?(Integer) rescue false)
        attachment_id = entry[:originalId]
      elsif entry[:originalId].present? && entry[:originalId].length >= 2
        partner_data = Rack::Utils.parse_nested_query(entry[:originalId]).with_indifferent_access
        attachment_id = partner_data[:attachment_id] if partner_data[:attachment_id].present?
      end
      attachment = Attachment.where(id: attachment_id).first if attachment_id
      account = root_account || Account.default
      mo = account.shard.activate { MediaObject.where(media_id: entry[:entryId]).first_or_initialize }
      mo.root_account ||= root_account || Account.default
      mo.title ||= entry[:name]
      if attachment
        mo.user_id ||= attachment.user_id
        mo.context = attachment.context
        mo.attachment_id = attachment.id
        attachment.update_attribute(:media_entry_id, entry[:entryId])
        # check for attachments that were created temporarily, just to import a media object
        if attachment.full_path.starts_with?(File.join(Folder::ROOT_FOLDER_NAME, CC::CCHelper::MEDIA_OBJECTS_FOLDER) + '/')
          attachment.destroy
        end
      end
      mo.context ||= mo.root_account
      mo.save
    end
  end

  def self.refresh_media_files(bulk_upload_id, attachment_ids, root_account_id, attempt=0)
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    res = client.bulkUploadGet(bulk_upload_id)
    if !res[:ready]
      if attempt < Setting.get('media_object_bulk_refresh_max_attempts', '5').to_i
        wait_period = Setting.get('media_object_bulk_refresh_wait_period', '30').to_i
        MediaObject.delay(run_at: wait_period.minutes.from_now, priority: Delayed::LOW_PRIORITY).
          refresh_media_files(bulk_upload_id, attachment_ids, root_account_id, attempt + 1)
      else
        # if it fails, then the attachment should no longer consider itself kalturable
        Attachment.where("id IN (?) OR root_attachment_id IN (?)", attachment_ids, attachment_ids).update_all(:media_entry_id => nil) unless attachment_ids.empty?
      end
      res
    else
      build_media_objects(res, root_account_id)
    end
  end

  def self.media_id_exists?(media_id)
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    info = client.mediaGet(media_id)
    return !!info&.dig(:id)
  end

  def self.ensure_media_object(media_id, **create_opts)
    if !by_media_id(media_id).any?
      delay(priority: Delayed::LOW_PRIORITY).create_if_id_exists(media_id, **create_opts)
    end
  end

  # typically call this in a delayed job, since it has to contact kaltura
  def self.create_if_id_exists(media_id, **create_opts)
    if media_id_exists?(media_id) && !by_media_id(media_id).any?
      create!(**create_opts.merge(:media_id => media_id))
    end
  end

  def update_title_on_kaltura
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    res = client.mediaUpdate(self.media_id, :name => self.user_entered_title)
    if !res[:error]
      self.title = self.user_entered_title
      self.save
    end
    res
  end

  def retrieve_details_later
    delay.retrieve_details_ensure_codecs
  end

  def media_sources
    CanvasKaltura::ClientV3.new.media_sources(self.media_id)
  end

  def retrieve_details_ensure_codecs(attempt=0)
    retrieve_details
    if !transcoded_details && self.created_at > 6.hours.ago
      if attempt < 10
        delay(run_at: (5 * attempt).minutes.from_now).retrieve_details_ensure_codecs(attempt + 1)
      else
        Canvas::Errors.capture(:media_object_failure, {
          message: "Kaltura flavor retrieval failed",
          object: self.inspect.to_s,
        })
      end
    end
  end

  def name<commit_msg>stop telling sentry about missing kaltura files<commit_after># frozen_string_literal: true

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

require 'csv'

class MediaObject < ActiveRecord::Base
  include Workflow
  include SearchTermHelper
  belongs_to :user
  belongs_to :context, polymorphic:
    [:course, :group, :conversation_message, :account, :assignment,
     :assessment_question, { context_user: 'User' }], exhaustive: false
  belongs_to :attachment
  belongs_to :root_account, :class_name => 'Account'

  validates_presence_of :media_id, :workflow_state
  has_many :media_tracks, -> { order(:locale) }, dependent: :destroy
  after_create :retrieve_details_later
  after_save :update_title_on_kaltura_later
  serialize :data

  attr_accessor :podcast_associated_asset

  def user_entered_title=(val)
    @push_user_title = true
    write_attribute(:user_entered_title, val)
  end

  def update_title_on_kaltura_later
    delay.update_title_on_kaltura if @push_user_title
    @push_user_title = nil
  end

  def self.find_by_media_id(media_id)
    unless Rails.env.production?
      raise "Do not look up MediaObjects by media_id - use the scope by_media_id instead to support migrated content."
    end
    super
  end


  set_policy do
    given { |user| (self.user && self.user == user) || (self.context && self.context.grants_right?(user, :manage_content)) }
    can :add_captions and can :delete_captions
  end

  # if wait_for_completion is true, this will wait SYNCHRONOUSLY for the bulk
  # upload to complete. Wrap it in a timeout if you ever want it to give up
  # waiting.
  def self.add_media_files(attachments, wait_for_completion)
    KalturaMediaFileHandler.new.add_media_files(attachments, wait_for_completion)
  end

  def self.bulk_migration(csv, root_account_id)
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    res = client.bulkUploadCsv(csv)
    if !res[:ready]
      MediaObject.delay(run_at: 1.minute.from_now, priority: Delayed::LOW_PRIORITY).
        refresh_media_files(res[:id], [], root_account_id)
    else
      build_media_objects(res, root_account_id)
    end
    res
  end

  def self.migration_csv(media_objects)
    CSV.generate do |csv|
      media_objects.each do |mo|
        mo.retrieve_details unless mo.data[:download_url]
        if mo.data[:download_url]
          row = []
          row << mo.title
          row << ""
          row << "old_id_#{mo.media_id}"
          row << mo.data[:download_url]
          row << mo.media_type.capitalize
          csv << row
        end
      end
    end
  end

  def self.build_media_objects(data, root_account_id)
    root_account = Account.where(id: root_account_id).first
    data[:entries].each do |entry|
      attachment_id = nil
      if entry[:originalId].present? && (Integer(entry[:originalId]).is_a?(Integer) rescue false)
        attachment_id = entry[:originalId]
      elsif entry[:originalId].present? && entry[:originalId].length >= 2
        partner_data = Rack::Utils.parse_nested_query(entry[:originalId]).with_indifferent_access
        attachment_id = partner_data[:attachment_id] if partner_data[:attachment_id].present?
      end
      attachment = Attachment.where(id: attachment_id).first if attachment_id
      account = root_account || Account.default
      mo = account.shard.activate { MediaObject.where(media_id: entry[:entryId]).first_or_initialize }
      mo.root_account ||= root_account || Account.default
      mo.title ||= entry[:name]
      if attachment
        mo.user_id ||= attachment.user_id
        mo.context = attachment.context
        mo.attachment_id = attachment.id
        attachment.update_attribute(:media_entry_id, entry[:entryId])
        # check for attachments that were created temporarily, just to import a media object
        if attachment.full_path.starts_with?(File.join(Folder::ROOT_FOLDER_NAME, CC::CCHelper::MEDIA_OBJECTS_FOLDER) + '/')
          attachment.destroy
        end
      end
      mo.context ||= mo.root_account
      mo.save
    end
  end

  def self.refresh_media_files(bulk_upload_id, attachment_ids, root_account_id, attempt=0)
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    res = client.bulkUploadGet(bulk_upload_id)
    if !res[:ready]
      if attempt < Setting.get('media_object_bulk_refresh_max_attempts', '5').to_i
        wait_period = Setting.get('media_object_bulk_refresh_wait_period', '30').to_i
        MediaObject.delay(run_at: wait_period.minutes.from_now, priority: Delayed::LOW_PRIORITY).
          refresh_media_files(bulk_upload_id, attachment_ids, root_account_id, attempt + 1)
      else
        # if it fails, then the attachment should no longer consider itself kalturable
        Attachment.where("id IN (?) OR root_attachment_id IN (?)", attachment_ids, attachment_ids).update_all(:media_entry_id => nil) unless attachment_ids.empty?
      end
      res
    else
      build_media_objects(res, root_account_id)
    end
  end

  def self.media_id_exists?(media_id)
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    info = client.mediaGet(media_id)
    return !!info&.dig(:id)
  end

  def self.ensure_media_object(media_id, **create_opts)
    if !by_media_id(media_id).any?
      delay(priority: Delayed::LOW_PRIORITY).create_if_id_exists(media_id, **create_opts)
    end
  end

  # typically call this in a delayed job, since it has to contact kaltura
  def self.create_if_id_exists(media_id, **create_opts)
    if media_id_exists?(media_id) && !by_media_id(media_id).any?
      create!(**create_opts.merge(:media_id => media_id))
    end
  end

  def update_title_on_kaltura
    client = CanvasKaltura::ClientV3.new
    client.startSession(CanvasKaltura::SessionType::ADMIN)
    res = client.mediaUpdate(self.media_id, :name => self.user_entered_title)
    if !res[:error]
      self.title = self.user_entered_title
      self.save
    end
    res
  end

  def retrieve_details_later
    delay.retrieve_details_ensure_codecs
  end

  def media_sources
    CanvasKaltura::ClientV3.new.media_sources(self.media_id)
  end

  def retrieve_details_ensure_codecs(attempt=0)
    retrieve_details
    if !transcoded_details && self.created_at > 6.hours.ago
      if attempt < 10
        delay(run_at: (5 * attempt).minutes.from_now).retrieve_details_ensure_codecs(attempt + 1)
      else
        Canvas::Errors.capture(:media_object_failure, {
          message: "Kaltura flavor retrieval failed",
          object: self.inspect.to_s,
        }, :warn)
      end
    end
  end

  def name
{'fraction_non_alphanumeric': 0.09147895570710202, 'fraction_numerical': 0.003168698766962871, 'mean_word_length': 3.7197659297789336, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2819864', 'n_tokens_mistral': 4679, 'n_tokens_neox': 4548, 'n_words': 1060}