canvas-lms/lib/cc/cc_exporter.rb

218 lines
7.3 KiB
Ruby

#
# 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 'action_controller_test_process'
module CC
class CCExporter
include TextHelper
ZIP_DIR = 'zip_dir'
attr_accessor :course, :user, :export_dir, :manifest, :zip_file, :for_course_copy, :for_master_migration
delegate :add_error, :add_item_to_export, :to => :@content_export, :allow_nil => true
def initialize(content_export, opts={})
@content_export = content_export
@course = opts[:course] || @content_export.context
raise "CCExporter supports only Courses" unless @course.is_a?(Course) # a Course is a Course, of course, of course
@user = opts[:user] || @content_export.user
@export_dir = nil
@manifest = nil
@zip_file = nil
@zip_name = nil
@logger = Rails.logger
@migration_config = ConfigFile.load('external_migration')
@migration_config ||= {:keep_after_complete => false}
@for_course_copy = opts[:for_course_copy]
@qti_only_export = @content_export && @content_export.qti_export?
@manifest_opts = opts.slice(:version)
@deletions = opts[:deletions]
@for_master_migration = true if @content_export && @content_export.for_master_migration?
end
def self.export(content_export, opts={})
exporter = CCExporter.new(content_export, opts)
exporter.export
end
def export
begin
if for_external_migration? && !@content_export.selective_export?
# we already know we're exporting all the data so we can begin the external exports now
@pending_exports = Canvas::Migration::ExternalContent::Migrator.begin_exports(@course)
end
create_export_dir
create_zip_file
if @qti_only_export
@manifest = CC::QTI::QTIManifest.new(self)
else
@manifest = Manifest.new(self, @manifest_opts)
end
@manifest.create_document
@manifest.close
if for_external_migration?
if @content_export.selective_export?
# if it's selective, we have to wait until we've completed the rest of the export
# before we really know what we exported. because magic
@pending_exports = Canvas::Migration::ExternalContent::Migrator.begin_exports(@course,
:selective => true, :exported_assets => @content_export.exported_assets.to_a)
end
external_content = Canvas::Migration::ExternalContent::Migrator.retrieve_exported_content(@content_export, @pending_exports)
write_external_content(external_content)
end
@export_dirs = [@export_dir]
if @for_master_migration
# for efficiency to the max, short-circuit the usual course copy process (i.e. zip up, save, and then unzip again)
# and instead go straight to the intermediate json
converter = CC::Importer::Canvas::Converter.new(:unzipped_file_path => @export_dir, :deletions => @deletions)
@export_dirs << converter.base_export_dir # make sure we clean this up too afterwards
converter.export
@export_path = converter.course["full_export_file_path"] # this is the course_export.json
@export_type = 'application/json'
else
copy_all_to_zip
@zip_file.close
@export_path = @zip_path
end
if @content_export && File.exist?(@export_path)
att = Attachment.new
att.context = @content_export
att.user = @content_export.user
data = Rack::Test::UploadedFile.new(@export_path, @export_type || Attachment.mimetype(@export_path))
Attachments::Storage.store_for_attachment(att, data)
if att.save
@content_export.attachment = att
@content_export.save
end
end
rescue
add_error(I18n.t('course_exports.errors.course_export', "Error running course export."), $!)
@logger.error $!
return false
ensure
@zip_file.close if @zip_file
if !@migration_config[:keep_after_complete]
@export_dirs.each do |export_dir|
FileUtils::rm_rf(export_dir) if File.directory?(export_dir)
end
end
end
true
end
def write_external_content(external_content)
return unless external_content.present?
folder = File.join(@export_dir, CCHelper::EXTERNAL_CONTENT_FOLDER)
FileUtils::mkdir_p(folder)
external_content.each do |service_key, data|
path = File.join(folder, "#{service_key}.json")
File.write(path, data.to_json)
end
end
def referenced_files
@manifest ? @manifest.referenced_files : {}
end
def set_progress(progress)
@content_export.fast_update_progress(progress) if @content_export
end
def errors
@content_export ? @content_export.error_messages : []
end
def export_id
@content_export ? @content_export.id : nil
end
def create_key(*args)
@content_export ? @content_export.create_key(*args) : CCHelper.create_key(*args)
end
def export_object?(obj, asset_type=nil)
@content_export ? @content_export.export_object?(obj, asset_type) : true
end
def add_exported_asset(obj)
@content_export && @content_export.add_exported_asset(obj)
end
def export_symbol?(obj)
@content_export ? @content_export.export_symbol?(obj) : true
end
def epub_export?
@content_export ? @content_export.epub_export.present? : nil
end
def for_external_migration?
@content_export && !(@qti_only_export || epub_export?)
end
private
def copy_all_to_zip
Dir["#{@export_dir}/**/**"].each do |file|
file_path = file.sub(@export_dir+'/', '')
next if file_path.starts_with? ZIP_DIR
@zip_file.add(file_path, file)
end
end
def create_export_dir
slug = "common_cartridge_#{@course.id}_user_#{@user.id}"
if @migration_config[:data_folder]
folder = @migration_config[:data_folder]
else
folder = Dir.tmpdir
end
@export_dir = File.join(folder, slug)
i = 1
while File.exist?(@export_dir) && File.directory?(@export_dir)
i += 1
@export_dir = File.join(folder, "#{slug}_attempt_#{i}")
end
FileUtils::mkdir_p @export_dir
@export_dir
end
def create_zip_file
name = CanvasTextHelper.truncate_text(@course.name.to_url, {:max_length => 200, :ellipsis => ''})
if @qti_only_export
@zip_name = "#{name}-quiz-export.zip"
else
@zip_name = "#{name}-export.#{CCHelper::CC_EXTENSION}"
end
FileUtils::mkdir_p File.join(@export_dir, ZIP_DIR)
@zip_path = File.join(@export_dir, ZIP_DIR, @zip_name)
@zip_file = Zip::File.new(@zip_path, Zip::File::CREATE)
end
end
end