Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed upload job limiter #386

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion amd/build/block_index.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/block_index.min.js.map

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions amd/src/block_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,47 @@ define(['jquery', 'core/modal_factory', 'core/modal_events',
}
});
};
/*
* Initalizes the unarchive uploadjob package on button click with modal.
*/
var initUnarchiveUploadJobModal = function(ocinstanceid, langstrings, contextid, liveupdate) {
$('.unarchive-uploadjob').on('click', function(e) {
e.preventDefault();
var targetBtn = $(e.currentTarget);
var uploadjobid = targetBtn.data('id');
ModalFactory.create({
type: ModalFactory.types.SAVE_CANCEL,
title: langstrings[9],
body: langstrings[10]
})
.then(function(modal) {
// Pause the live update if it is running.
pauseLiveUpdate(liveupdate);
modal.setSaveButtonText(langstrings[11]);
var root = modal.getRoot();
root.on(ModalEvents.save, function (e) {
Ajax.call([{
methodname: 'block_opencast_unarchive_uploadjob',
args: {contextid: contextid, ocinstanceid: ocinstanceid, uploadjobid: uploadjobid},
done: function() {
window.location.reload();
},
fail: Notification.exception
}]);
e.preventDefault();
});
root.on(ModalEvents.hidden, function() {
// Resume the live update if it was paused.
resumeLiveUpdate(ocinstanceid, contextid, liveupdate);
// Destroy when hidden/closed.
modal.destroy();
});
modal.show();
return;
})
.catch(Notification.exception);
});
};

/*
* Initialise all of the modules for the opencast block.
Expand Down Expand Up @@ -494,11 +535,24 @@ define(['jquery', 'core/modal_factory', 'core/modal_events',
{
key: 'startworkflow_modal_configpanel_title',
component: 'block_opencast'
},
{
key: 'unarchiveuploadjob',
component: 'block_opencast'
},
{
key: 'unarchiveuploadjobconfirmtext',
component: 'block_opencast'
},
{
key: 'unarchiveuploadjobconfirmbtn_save',
component: 'block_opencast'
}
];
str.get_strings(strings).then(function(results) {
initWorkflowModal(ocinstanceid, courseid, results, contextid, liveupdate);
initReportModal(ocinstanceid, courseid, results, contextid, liveupdate);
initUnarchiveUploadJobModal(ocinstanceid, results, contextid, liveupdate);
return;
}).catch(Notification.exception);
window.addEventListener('message', function(event) {
Expand Down
5 changes: 5 additions & 0 deletions classes/event/upload_failed.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ public static function get_name() {
* @return string
*/
public function get_description() {
$archived = "";
if (isset($this->data['other']['archived']) && $this->data['other']['archived'] === true) {
$archived = " the upload job has now been archived, ";
}
return "The upload of {$this->data['other']['filename']} (Course: {$this->data['courseid']}) " .
"to opencast instance {$this->data['ocinstanceid']} failed {$this->data['other']['countfailed']} times, " .
$archived .
"Reason: {$this->data['other']['errormessage']}";
}

Expand Down
66 changes: 66 additions & 0 deletions classes/external.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use block_opencast\local\apibridge;
use block_opencast\local\series_form;
use block_opencast\local\liveupdate_helper;
use block_opencast\local\upload_helper;
use tool_opencast\seriesmapping;

defined('MOODLE_INTERNAL') || die;
Expand Down Expand Up @@ -121,6 +122,19 @@ public static function get_liveupdate_info_parameters() {
]);
}

/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function unarchive_uploadjob_parameters() {
return new external_function_parameters([
'contextid' => new external_value(PARAM_INT, 'The context id for the course'),
'ocinstanceid' => new external_value(PARAM_INT, 'The Opencast instance id'),
'uploadjobid' => new external_value(PARAM_INT, 'The upload job id'),
]);
}

/**
* Submits the series form.
*
Expand Down Expand Up @@ -417,6 +431,49 @@ public static function get_liveupdate_info(int $contextid, int $ocinstanceid, st
return json_encode($liveupdateinfo);
}

/**
* Perform unarchiving an uploadjob.
*
* @param int $contextid The context id for the course.
* @param int $ocinstanceid Opencast instance id
* @param int $uploadjobid Uploadjob id
*
* @return string Latest update state info
*/
public static function unarchive_uploadjob(int $contextid, int $ocinstanceid, int $uploadjobid) {
global $USER, $DB;
$params = self::validate_parameters(self::unarchive_uploadjob_parameters(), [
'contextid' => $contextid,
'ocinstanceid' => $ocinstanceid,
'uploadjobid' => $uploadjobid,
]);

$context = context::instance_by_id($params['contextid']);
self::validate_context($context);
require_capability('block/opencast:addvideo', $context);

list($unused, $course, $cm) = get_context_info_array($context->id);

$params = [
'id' => $params['uploadjobid'],
'ocinstanceid' => $params['ocinstanceid'],
'courseid' => $course->id,
'status' => upload_helper::STATUS_ARCHIVED_FAILED_UPLOAD,
];
$uploadjob = $DB->get_record('block_opencast_uploadjob', $params);

if (!empty($uploadjob)) {
$time = time();
$uploadjob->timemodified = $time;
$uploadjob->countfailed = 0;
$uploadjob->status = upload_helper::STATUS_READY_TO_UPLOAD;
$DB->update_record('block_opencast_uploadjob', $uploadjob);
return true;
}

throw new moodle_exception('uploadjobnotfound', 'block_opencast');
}

/**
* Returns description of method result value
*
Expand Down Expand Up @@ -470,4 +527,13 @@ public static function set_default_series_returns() {
public static function get_liveupdate_info_returns() {
return new external_value(PARAM_RAW, 'Json live update info');
}

/**
* Returns description of method result value
*
* @return external_description
*/
public static function unarchive_uploadjob_returns() {
return new external_value(PARAM_BOOL, 'True if successful');
}
}
47 changes: 45 additions & 2 deletions classes/local/eventstatus_notification_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,10 @@ public static function notify_users_upload_queue($job, $metadata) {
}
}

$where = 'status <> :status';
$where = 'status NOT IN (:statustransferred, :statusarchived)';
$params = [
'status' => upload_helper::STATUS_TRANSFERRED,
'statustransferred' => upload_helper::STATUS_TRANSFERRED,
'statusarchived' => upload_helper::STATUS_ARCHIVED_FAILED_UPLOAD,
];
$allqueuednum = $DB->count_records_select('block_opencast_uploadjob', $where, $params);
$waitingnum = 0;
Expand All @@ -259,6 +260,48 @@ public static function notify_users_upload_queue($job, $metadata) {
}
}


/**
* Notify users about the failed upload being archived.
*
* @param object $job represents the upload job.
* @param string $title event title or filename.
*
*/
public static function notify_users_archived_upload($job, $title) {
global $DB;
// Initialize the user list as an empty array.
$usertolist = [];
// Add uploader user object to the user list.
$usertolist[] = $DB->get_record('user', ['id' => $job->userid]);

// Get admin config to check if all teachers of the course should be notified as well.
$notifyteachers = get_config('block_opencast', 'eventstatusnotifyteachers_' . $job->ocinstanceid);
if ($notifyteachers) {
// Get the role of teachers.
$role = $DB->get_record('role', ['shortname' => 'editingteacher']);
// Get the course context.
$context = context_course::instance($job->courseid);
// Get the teachers based on their role in the course context.
$teachers = get_role_users($role->id, $context);

// If teachers array list is not empty, we add them to the user list.
if (!empty($teachers)) {
foreach ($teachers as $teacher) {
// We need to make sure that the uploader is not in the teachers list.
if ($teacher->id != $job->userid) {
$usertolist[] = $DB->get_record('user', ['id' => $teacher->id]);
}
}
}
}

// Notify users one by one.
foreach ($usertolist as $userto) {
notifications::notify_archived_upload($job->courseid, $userto, $title);
}
}

/**
* Get the relative status text based on status code.
*
Expand Down
3 changes: 2 additions & 1 deletion classes/local/liveupdate_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ public static function get_uploading_info($uploadjobid) {
$info['replace'] = $status;
// We pass remove param, to decide whether to continue checking that item or not.
$remove = false;
if ($uploadjob->status == upload_helper::STATUS_TRANSFERRED) {
if ($uploadjob->status == upload_helper::STATUS_TRANSFERRED ||
$uploadjob->status == upload_helper::STATUS_ARCHIVED_FAILED_UPLOAD) {
// We remove the item from live update when the upload is transferred.
$remove = true;
}
Expand Down
25 changes: 25 additions & 0 deletions classes/local/notifications.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,4 +491,29 @@ public static function notify_incompleted_import_mapping_records($courseid, $inc
$admin = get_admin();
self::send_message('error', $admin, $subject, $body);
}

/**
* Notify user about archiving an upload job.
* @param int $courseid Course id
* @param object $touser User to which notification is sent
* @param string $title the title or filename of the video
*/
public static function notify_archived_upload($courseid, $touser, $title) {
global $DB;

$a = (object)[
'courseid' => $courseid,
'coursefullname' => get_string('coursefullnameunknown', 'block_opencast'),
'title' => $title,
];

if ($course = $DB->get_record('course', ['id' => $courseid])) {
$a->coursefullname = $course->fullname;
}

$subject = get_string('notificationuploadarchived_subj', 'block_opencast');
$body = get_string('notificationuploadarchived_body', 'block_opencast', $a);

self::send_message('opencasteventstatus_notification', $touser, $subject, $body);
}
}
54 changes: 47 additions & 7 deletions classes/local/upload_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ class upload_helper {
/** @var int Video is successfully transferred to Opencast. */
const STATUS_TRANSFERRED = 40;

/** @var int Upload job is archived due to limited failed attempts */
const STATUS_ARCHIVED_FAILED_UPLOAD = 11;

/**
* Get explaination string for status code
* @param int $statuscode Status code
Expand All @@ -92,6 +95,8 @@ public static function get_status_string($statuscode) {
return get_string('mstateuploaded', 'block_opencast');
case self::STATUS_TRANSFERRED :
return get_string('mstatetransferred', 'block_opencast');
case self::STATUS_ARCHIVED_FAILED_UPLOAD :
return get_string('mstatearchived', 'block_opencast');
default :
return '';
}
Expand Down Expand Up @@ -296,10 +301,14 @@ public static function save_upload_jobs($ocinstanceid, $courseid, $options, $vis
*/
public static function delete_video_draft($jobtodelete) {
global $DB;
// Check again shortly before deletion if the status is still STATUS_READY_TO_UPLOAD.
if ($DB->record_exists('block_opencast_uploadjob',
['id' => $jobtodelete->id, 'status' => self::STATUS_READY_TO_UPLOAD])) {

// Check again shortly before deletion if the status is still STATUS_READY_TO_UPLOAD or STATUS_ARCHIVED_FAILED_UPLOAD.
$selectwhere = "id = :id AND status IN (:statustransferred, :statusarchived)";
$params = [
'id' => $jobtodelete->id,
'statustransferred' => self::STATUS_READY_TO_UPLOAD,
'statusarchived' => self::STATUS_ARCHIVED_FAILED_UPLOAD,
];
if ($DB->record_exists_select('block_opencast_uploadjob', $selectwhere, $params)) {
$DB->delete_records('block_opencast_uploadjob', ['id' => $jobtodelete->id]);
$DB->delete_records('block_opencast_metadata', ['uploadjobid' => $jobtodelete->id]);
// Delete from files table.
Expand Down Expand Up @@ -419,7 +428,16 @@ protected function upload_failed($job, $errormessage) {
// Update the job to enqueue again.
$job->countfailed++;
$job->timemodified = time();
$job->status = self::STATUS_READY_TO_UPLOAD;

$failedretrylimit = (int) get_config('block_opencast', 'faileduploadretrylimit');
$isarchived = false;
$status = self::STATUS_READY_TO_UPLOAD;
if ($failedretrylimit > 0 && $job->countfailed >= $failedretrylimit) {
$status = self::STATUS_ARCHIVED_FAILED_UPLOAD;
$isarchived = true;
}

$job->status = $status;

$DB->update_record('block_opencast_uploadjob', $job);

Expand Down Expand Up @@ -447,11 +465,26 @@ protected function upload_failed($job, $errormessage) {
'errormessage' => $errormessage,
'countfailed' => $job->countfailed,
'ocinstanceid' => $job->ocinstanceid,
'archived' => $isarchived,
],
]
);

$event->trigger();

// Notify users about archived upload job.
$notificationenabled = get_config('block_opencast', 'eventstatusnotificationenabled_' . $job->ocinstanceid);
if ($notificationenabled && $isarchived) {
// Prepare title with default filename.
$title = implode(' & ', $filenames);
if (isset($job->metadata)) {
$metadata = json_decode($job->metadata);
$title = $metadata[array_search('title', array_column($metadata, 'id'))]->value;
}

// Notify user about putting the upload into archive status.
eventstatus_notification_helper::notify_users_archived_upload($job, $title);
}
}

/**
Expand Down Expand Up @@ -686,15 +719,22 @@ public function cron() {
$ocinstances = settings_api::get_ocinstances();
foreach ($ocinstances as $ocinstance) {
// Get all waiting jobs.
$sql = "SELECT * FROM {block_opencast_uploadjob} WHERE status < ? AND ocinstanceid = ? ORDER BY timemodified ASC ";
$sql = "SELECT * FROM {block_opencast_uploadjob}" .
" WHERE status < ? AND status <> ? AND ocinstanceid = ? ORDER BY timemodified ASC ";

$limituploadjobs = get_config('block_opencast', 'limituploadjobs_' . $ocinstance->id);

if (!$limituploadjobs) {
$limituploadjobs = 0;
}

$jobs = $DB->get_records_sql($sql, [self::STATUS_TRANSFERRED, $ocinstance->id], 0, $limituploadjobs);
$params = [
self::STATUS_TRANSFERRED,
self::STATUS_ARCHIVED_FAILED_UPLOAD,
$ocinstance->id,
];

$jobs = $DB->get_records_sql($sql, $params, 0, $limituploadjobs);

if (!$jobs) {
mtrace('...no jobs to proceed for instance "' . $ocinstance->name . '"');
Expand Down
Loading
Loading