2017-04-28 03:44:20 +08:00
|
|
|
#
|
|
|
|
# Copyright (C) 2015 - 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/>.
|
|
|
|
|
2015-07-21 05:19:44 +08:00
|
|
|
class EpubExport < ActiveRecord::Base
|
2015-09-17 07:05:30 +08:00
|
|
|
include CC::Exporter::Epub::Exportable
|
2016-05-04 04:05:16 +08:00
|
|
|
include LocaleSelection
|
2015-07-21 05:19:44 +08:00
|
|
|
include Workflow
|
|
|
|
|
|
|
|
belongs_to :content_export
|
|
|
|
belongs_to :course
|
|
|
|
belongs_to :user
|
2016-12-23 04:21:33 +08:00
|
|
|
has_many :attachments, -> { order('created_at DESC') }, dependent: :destroy, as: :context, inverse_of: :context, class_name: 'Attachment'
|
|
|
|
has_one :epub_attachment, -> { where(content_type: 'application/epub+zip').order('created_at DESC') }, as: :context, inverse_of: :context, class_name: 'Attachment'
|
|
|
|
has_one :zip_attachment, -> { where(content_type: 'application/zip').order('created_at DESC') }, as: :context, inverse_of: :context, class_name: 'Attachment'
|
|
|
|
has_one :job_progress, as: :context, inverse_of: :context, class_name: 'Progress'
|
2015-07-21 05:19:44 +08:00
|
|
|
validates :course_id, :workflow_state, presence: true
|
2017-04-06 00:33:25 +08:00
|
|
|
has_a_broadcast_policy
|
|
|
|
alias_attribute :context, :course # context is needed for the content export notification
|
2015-07-21 05:19:44 +08:00
|
|
|
|
|
|
|
PERCENTAGE_COMPLETE = {
|
|
|
|
created: 0,
|
2015-12-17 07:15:39 +08:00
|
|
|
exported: 80,
|
|
|
|
generating: 90,
|
2015-07-21 05:19:44 +08:00
|
|
|
generated: 100
|
|
|
|
}.freeze
|
|
|
|
|
2015-12-17 07:15:39 +08:00
|
|
|
def update_progress_from_content_export!(val)
|
|
|
|
multiplier = PERCENTAGE_COMPLETE[:exported].to_f / 100
|
|
|
|
n = val * multiplier
|
|
|
|
self.job_progress.update_completion!(n.to_i)
|
|
|
|
end
|
|
|
|
|
|
|
|
workflow do
|
|
|
|
state :created
|
|
|
|
state :exporting
|
|
|
|
state :exported
|
|
|
|
state :generating
|
|
|
|
state :generated
|
2015-07-21 05:19:44 +08:00
|
|
|
state :failed
|
|
|
|
state :deleted
|
|
|
|
end
|
|
|
|
|
2017-04-06 00:33:25 +08:00
|
|
|
set_broadcast_policy do |p|
|
|
|
|
p.dispatch :content_export_finished
|
|
|
|
p.to { [user] }
|
|
|
|
p.whenever do |record|
|
|
|
|
record.changed_state(:generated)
|
|
|
|
end
|
|
|
|
|
|
|
|
p.dispatch :content_export_failed
|
|
|
|
p.to { [user] }
|
|
|
|
p.whenever do |record|
|
|
|
|
record.changed_state(:failed)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-21 05:19:44 +08:00
|
|
|
after_create do
|
2016-12-14 07:08:27 +08:00
|
|
|
create_job_progress(completion: 0, tag: self.class.to_s.underscore)
|
2015-07-21 05:19:44 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
delegate :download_url, to: :attachment, allow_nil: true
|
|
|
|
delegate :completion, :running?, to: :job_progress, allow_nil: true
|
|
|
|
|
|
|
|
scope :running, -> { where(workflow_state: ['created', 'exporting', 'exported', 'generating']) }
|
|
|
|
scope :visible_to, ->(user) { where(user_id: user) }
|
|
|
|
|
|
|
|
set_policy do
|
|
|
|
given do |user|
|
|
|
|
course.grants_right?(user, :read_as_admin) ||
|
|
|
|
course.grants_right?(user, :participate_as_student)
|
|
|
|
end
|
|
|
|
can :create
|
|
|
|
|
|
|
|
given do |user|
|
|
|
|
self.user == user || course.grants_right?(user, :read_as_admin)
|
|
|
|
end
|
|
|
|
can :read
|
|
|
|
|
|
|
|
given do |user|
|
|
|
|
grants_right?(user, :read) && generated?
|
|
|
|
end
|
|
|
|
can :download
|
|
|
|
|
|
|
|
given do |user|
|
|
|
|
[ 'generated', 'failed' ].include?(workflow_state) &&
|
|
|
|
self.grants_right?(user, :create)
|
|
|
|
end
|
|
|
|
can :regenerate
|
|
|
|
end
|
|
|
|
|
|
|
|
def export
|
|
|
|
create_content_export!({
|
|
|
|
user: user,
|
|
|
|
export_type: ContentExport::COMMON_CARTRIDGE,
|
|
|
|
selected_content: { :everything => true },
|
|
|
|
progress: 0,
|
|
|
|
context: course
|
|
|
|
})
|
|
|
|
job_progress.start
|
|
|
|
update_attribute(:workflow_state, 'exporting')
|
|
|
|
content_export.export
|
|
|
|
true
|
|
|
|
end
|
|
|
|
handle_asynchronously :export, priority: Delayed::LOW_PRIORITY, max_attempts: 1
|
|
|
|
|
|
|
|
def mark_exported
|
|
|
|
if content_export.failed?
|
2015-09-17 07:05:30 +08:00
|
|
|
mark_as_failed
|
2015-07-21 05:19:44 +08:00
|
|
|
else
|
|
|
|
update_attribute(:workflow_state, 'exported')
|
|
|
|
job_progress.update_attribute(:completion, PERCENTAGE_COMPLETE[:exported])
|
|
|
|
generate
|
|
|
|
end
|
|
|
|
end
|
|
|
|
handle_asynchronously :mark_exported, priority: Delayed::LOW_PRIORITY, max_attempts: 1
|
|
|
|
|
|
|
|
def generate
|
|
|
|
job_progress.update_attribute(:completion, PERCENTAGE_COMPLETE[:generating])
|
|
|
|
update_attribute(:workflow_state, 'generating')
|
2015-09-17 07:05:30 +08:00
|
|
|
convert_to_epub
|
2015-07-21 05:19:44 +08:00
|
|
|
end
|
|
|
|
handle_asynchronously :generate, priority: Delayed::LOW_PRIORITY, max_attempts: 1
|
|
|
|
|
2015-09-17 07:05:30 +08:00
|
|
|
def mark_as_generated
|
2015-07-21 05:19:44 +08:00
|
|
|
job_progress.complete! if job_progress.running?
|
|
|
|
update_attribute(:workflow_state, 'generated')
|
|
|
|
end
|
|
|
|
|
2015-09-17 07:05:30 +08:00
|
|
|
def mark_as_failed
|
2015-07-21 05:19:44 +08:00
|
|
|
job_progress.try :fail!
|
|
|
|
update_attribute(:workflow_state, 'failed')
|
|
|
|
end
|
2015-09-17 07:05:30 +08:00
|
|
|
|
|
|
|
# Epub Exportable overrides
|
|
|
|
def content_cartridge
|
|
|
|
self.content_export.attachment
|
|
|
|
end
|
|
|
|
|
|
|
|
def convert_to_epub
|
2015-10-30 06:50:33 +08:00
|
|
|
begin
|
2016-05-04 04:05:16 +08:00
|
|
|
set_locale
|
2015-10-30 06:50:33 +08:00
|
|
|
file_paths = super
|
2016-05-04 04:05:16 +08:00
|
|
|
I18n.locale = :en
|
2015-09-23 05:51:25 +08:00
|
|
|
rescue => e
|
2015-10-30 06:50:33 +08:00
|
|
|
mark_as_failed
|
|
|
|
raise e
|
|
|
|
end
|
2015-10-24 02:41:15 +08:00
|
|
|
|
|
|
|
file_paths.each do |file_path|
|
2015-10-27 01:09:27 +08:00
|
|
|
create_attachment_from_path!(file_path)
|
2015-09-17 07:05:30 +08:00
|
|
|
end
|
2015-10-24 02:41:15 +08:00
|
|
|
mark_as_generated
|
2015-10-27 01:09:27 +08:00
|
|
|
file_paths.each {|file_path| cleanup_file_path!(file_path) }
|
2015-09-17 07:05:30 +08:00
|
|
|
end
|
|
|
|
handle_asynchronously :convert_to_epub, priority: Delayed::LOW_PRIORITY, max_attempts: 1
|
|
|
|
|
2015-10-27 01:09:27 +08:00
|
|
|
def create_attachment_from_path!(file_path)
|
|
|
|
begin
|
|
|
|
mime_type = MIME::Types.type_for(file_path).first
|
|
|
|
file = Rack::Multipart::UploadedFile.new(
|
|
|
|
file_path,
|
|
|
|
mime_type.try(:content_type)
|
|
|
|
)
|
|
|
|
self.attachments.create({
|
|
|
|
filename: File.basename(file_path),
|
|
|
|
uploaded_data: file
|
|
|
|
})
|
|
|
|
rescue Errno::ENOENT => e
|
|
|
|
mark_as_failed
|
|
|
|
raise e
|
|
|
|
ensure
|
|
|
|
file.try(:close)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def cleanup_file_path!(file_path)
|
|
|
|
FileUtils.rm_rf(file_path, secure: true) if File.exist?(file_path)
|
|
|
|
end
|
|
|
|
|
2015-09-17 07:05:30 +08:00
|
|
|
def sort_by_content_type?
|
|
|
|
self.course.organize_epub_by_content_type
|
|
|
|
end
|
2016-05-04 04:05:16 +08:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def set_locale
|
|
|
|
I18n.locale = infer_locale(
|
|
|
|
context: course,
|
|
|
|
user: user,
|
|
|
|
root_account: course.root_account
|
|
|
|
)
|
|
|
|
end
|
2015-07-21 05:19:44 +08:00
|
|
|
end
|