1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace mod_bigbluebuttonbn\task; 18 19 use core\task\adhoc_task; 20 use core\task\manager; 21 use Matrix\Exception; 22 use mod_bigbluebuttonbn\instance; 23 use mod_bigbluebuttonbn\local\proxy\recording_proxy; 24 use mod_bigbluebuttonbn\logger; 25 use mod_bigbluebuttonbn\recording; 26 use moodle_exception; 27 28 /** 29 * Class containing the scheduled task for converting recordings for the BigBlueButton version 2.5 in Moodle 4.0. 30 * 31 * @package mod_bigbluebuttonbn 32 * @copyright 2021 Jesus Federico, Blindside Networks Inc <jesus at blindsidenetworks dot com> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class upgrade_recordings_task extends adhoc_task { 36 /** 37 * Run the migration task. 38 */ 39 public function execute() { 40 $info = $this->get_custom_data(); 41 $meetingid = $info->meetingid; 42 $isimported = $info->isimported ?? 0; 43 $this->process_bigbluebuttonbn_logs($meetingid, $isimported); 44 } 45 46 /** 47 * Process all bigbluebuttonbn logs looking for entries which should be converted to meetings. 48 * 49 * @param string $meetingid 50 * @param bool $isimported 51 * @return bool Whether any more logs are waiting to be processed 52 * @throws \dml_exception 53 * @throws moodle_exception 54 */ 55 protected function process_bigbluebuttonbn_logs(string $meetingid, bool $isimported): bool { 56 global $DB; 57 58 $classname = static::class; 59 mtrace("Executing {$classname} for meeting {$meetingid}..."); 60 61 // Fetch the logs queued for upgrade. 62 mtrace("Fetching logs for conversion"); 63 // Each log is ordered by timecreated. 64 [$select, $params] = $this->get_sql_query_for_logs($meetingid, $isimported); 65 $logsrs = $DB->get_recordset_select('bigbluebuttonbn_logs', 66 $select, 67 $params, 68 'timecreated DESC', 69 'id, meetingid, timecreated, log'); 70 71 if (!$logsrs->valid()) { 72 mtrace("No logs were found for conversion."); 73 // No more logs. Stop queueing. 74 return false; 75 } 76 // Retrieve recordings from the servers for this meeting. 77 $recordings = recording_proxy::fetch_recording_by_meeting_id([$meetingid]); 78 // Sort recordings by meetingId, then startTime. 79 uasort($recordings, function($a, $b) { 80 return $b['startTime'] - $a['startTime']; 81 }); 82 83 // Create an instance of bigbluebuttonbn_recording per valid recording. 84 mtrace("Creating new recording records..."); 85 $recordingcount = 0; 86 foreach ($recordings as $recordingid => $recording) { 87 $importeddata = $isimported ? '' : json_encode($recording); 88 try { 89 $instance = instance::get_from_meetingid($recording['meetingID']); 90 } catch (Exception $e) { 91 mtrace("Unable to parse meetingID " . $e->getMessage()); 92 continue; 93 } 94 95 if ($instance) { 96 $newrecording = [ 97 'courseid' => $instance->get_course_id(), 98 'bigbluebuttonbnid' => $instance->get_instance_id(), 99 'groupid' => $instance->get_group_id(), // The groupid should be taken from the meetingID. 100 'recordingid' => $recordingid, 101 'status' => recording::RECORDING_STATUS_PROCESSED, 102 ]; 103 } else { 104 mtrace("Unable to find an activity for {$recording['meetingID']}. This recording is headless."); 105 // This instance does not exist any more. 106 // Use the data in the log instead of the instance. 107 $meetingdata = instance::parse_meetingid($recording['meetingID']); 108 $newrecording = [ 109 'courseid' => $meetingdata['courseid'], 110 'bigbluebuttonbnid' => $meetingdata['instanceid'], 111 'groupid' => 0, 112 'recordingid' => $recordingid, 113 'status' => recording::RECORDING_STATUS_PROCESSED, 114 ]; 115 116 if (array_key_exists('groupid', $meetingdata)) { 117 $newrecording['groupid'] = $meetingdata['groupid']; 118 } 119 } 120 121 if ($DB->record_exists('bigbluebuttonbn_recordings', $newrecording)) { 122 mtrace("A recording already exists for {$recording['recordID']}. Skipping."); 123 // A recording matching these characteristics alreay exists. 124 continue; 125 } 126 // Recording has not been imported, check if we still have more logs. 127 // We try to guess which logs matches which recordings are they are classed in the same order. 128 // But this is just an attempt. 129 $log = null; 130 if ($logsrs->valid()) { 131 $log = $logsrs->current(); 132 $logsrs->next(); 133 } 134 $timecreated = empty($log) ? time() : $log->timecreated; 135 $newrecording['imported'] = $isimported; 136 $newrecording['headless'] = 0; 137 $newrecording['importeddata'] = $importeddata; 138 $newrecording['timecreated'] = $newrecording['timemodified'] = $timecreated; 139 140 // If we could not match with a log, we still create the recording. 141 $DB->insert_record('bigbluebuttonbn_recordings', $newrecording); 142 $recordingcount++; 143 } 144 mtrace("Migrated {$recordingcount} recordings."); 145 // Now deactivate logs by marking all of them as migrated. 146 // Reason for this is that we don't want to run another migration here and we don't know 147 // which logs matches which recordings. 148 $DB->set_field_select('bigbluebuttonbn_logs', 'log', 149 $isimported ? logger::EVENT_IMPORT_MIGRATED : logger::EVENT_CREATE_MIGRATED, 150 $select, 151 $params 152 ); 153 $logsrs->close(); 154 return true; 155 } 156 157 /** 158 * Get the query (records_select) for the logs to convert. 159 * 160 * Each log is ordered by timecreated. 161 * 162 * @param string $meetingid 163 * @param bool $isimported 164 * @return array 165 */ 166 protected function get_sql_query_for_logs(string $meetingid, bool $isimported): array { 167 global $DB; 168 if ($isimported) { 169 return [ 170 'log = :logmatch AND meetingid = :meetingid', 171 ['logmatch' => logger::EVENT_IMPORT, 'meetingid' => $meetingid], 172 ]; 173 } 174 return [ 175 'log = :logmatch AND meetingid = :meetingid AND ' . $DB->sql_like('meta', ':match'), 176 [ 177 'logmatch' => logger::EVENT_CREATE, 178 'match' => '%true%', 179 'meetingid' => $meetingid 180 ], 181 ]; 182 } 183 184 /** 185 * Schedule all upgrading tasks. 186 * 187 * @param bool $importedrecordings 188 * @return void 189 * @throws \dml_exception 190 */ 191 public static function schedule_upgrade_per_meeting($importedrecordings = false) { 192 global $DB; 193 $meetingids = $DB->get_fieldset_sql( 194 'SELECT DISTINCT meetingid FROM {bigbluebuttonbn_logs} WHERE log = :createorimport', 195 ['createorimport' => $importedrecordings ? logger::EVENT_IMPORT : logger::EVENT_CREATE] 196 ); 197 foreach ($meetingids as $mid) { 198 $createdrecordingtask = new static(); 199 $createdrecordingtask->set_custom_data((object) ['meetingid' => $mid, 'isimported' => $importedrecordings]); 200 manager::queue_adhoc_task($createdrecordingtask); 201 } 202 } 203 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body