MDL-81437 core_files: Add ad-hoc task to upgrade mimetypes on update
Signed-off-by: Daniel Ziegenberg <[email protected]>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
issueNumber: MDL-81437
|
||||
notes:
|
||||
core_files:
|
||||
- message: |
|
||||
Adds a new ad-hoc task `core_files\task\asynchronous_mimetype_upgrade_task` to upgrade the mimetype of files
|
||||
asynchronously during core upgrades. The upgradelib also comes with a new utility function
|
||||
`upgrade_create_async_mimetype_upgrade_task` for creating said ad-hoc task.
|
||||
type: improved
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_files\task;
|
||||
|
||||
use core\task\adhoc_task;
|
||||
|
||||
/**
|
||||
* Ad-hoc task that performs asynchronous upgrades of a given file type.
|
||||
*
|
||||
* This ad-hoc taks is used during core upgrades.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2025 Daniel Ziegenberg
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class asynchronous_mimetype_upgrade_task extends adhoc_task {
|
||||
|
||||
/**
|
||||
* Run the adhoc task and update the mime type of files.
|
||||
*/
|
||||
public function execute(): void {
|
||||
global $DB;
|
||||
|
||||
// Upgrade mime type for existing files.
|
||||
$customdata = $this->get_custom_data();
|
||||
|
||||
foreach ($customdata->extensions as $extension) {
|
||||
mtrace("Updating mime type for files with extension *.{$extension} to {$customdata->mimetype}");
|
||||
|
||||
$condition = $DB->sql_like('filename', ":extension", false);
|
||||
$select = "{$condition} AND mimetype <> :mimetype";
|
||||
$params = [
|
||||
'extension' => $DB->sql_like_escape("%.$extension"),
|
||||
'mimetype' => $customdata->mimetype,
|
||||
];
|
||||
|
||||
$count = $DB->count_records_select('files', $select, $params);
|
||||
$DB->set_field_select('files', 'mimetype', $customdata->mimetype, $select, $params);
|
||||
|
||||
mtrace("Updated {$count} files with extension *.{$extension} to {$customdata->mimetype}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_files\task;
|
||||
|
||||
/**
|
||||
* Tests for the asynchronous mimetype upgrade task.
|
||||
*
|
||||
* @package core_files
|
||||
* @category test
|
||||
* @copyright 2025 Daniel Ziegenberg
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_files\task\asynchronous_mimetype_upgrade_task::execute
|
||||
*/
|
||||
final class asynchronous_mimetype_upgrade_task_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for test_upgrade_mimetype().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function upgrade_mimetype_provider(): array {
|
||||
return [
|
||||
'Single file, one extension' => [
|
||||
'files' => [
|
||||
'filename.extension1' => 'type/subtype',
|
||||
],
|
||||
'mimetype' => 'type/subtype',
|
||||
'extensions' => ['extension1'],
|
||||
],
|
||||
'Single file, one extension, desired extension substring of actual extension' => [
|
||||
'files' => [
|
||||
'filename.extension1andsomemore' => 'text/plain',
|
||||
],
|
||||
'mimetype' => 'type/subtype',
|
||||
'extensions' => ['extension1'],
|
||||
],
|
||||
'Single file, one extension, filename same as desired extension' => [
|
||||
'files' => [
|
||||
'extension1.bogus' => 'text/plain',
|
||||
],
|
||||
'mimetype' => 'type/subtype',
|
||||
'extensions' => ['extension1'],
|
||||
],
|
||||
'Multiple files, one extension' => [
|
||||
'files' => [
|
||||
'filename_a.extension1' => 'type/subtype',
|
||||
'filename_b.extension1' => 'type/subtype',
|
||||
],
|
||||
'mimetype' => 'type/subtype',
|
||||
'extensions' => ['extension1'],
|
||||
],
|
||||
'Multiple files, multiple extensions' => [
|
||||
'files' => [
|
||||
'filename_a.extension1' => 'type/subtype',
|
||||
'filename_b.extension1' => 'type/subtype',
|
||||
'filename_a.extension2' => 'type/subtype',
|
||||
'filename_b.extension2' => 'type/subtype',
|
||||
],
|
||||
'mimetype' => 'type/subtype',
|
||||
'extensions' => [
|
||||
'extension1',
|
||||
'extension2',
|
||||
],
|
||||
],
|
||||
'Multiple files, multiple extensions, some unrelated' => [
|
||||
'files' => [
|
||||
'filename_a.extension1' => 'type/subtype',
|
||||
'filename_b.extension1' => 'type/subtype',
|
||||
'filename_a.extension2' => 'type/subtype',
|
||||
'filename_b.extension2' => 'type/subtype',
|
||||
'filename_c.bogus' => 'text/plain',
|
||||
'filename_c.bogus2' => 'text/plain',
|
||||
],
|
||||
'mimetype' => 'type/subtype',
|
||||
'extensions' => [
|
||||
'extension1',
|
||||
'extension2',
|
||||
'extension3',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test upgrading the mimetype of files.
|
||||
*
|
||||
* @dataProvider upgrade_mimetype_provider
|
||||
*
|
||||
* @param array $files
|
||||
* @param string $mimetype
|
||||
* @param array $extensions
|
||||
*/
|
||||
public function test_upgrade_mimetype(array $files, string $mimetype, array $extensions): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create files with different extensions.
|
||||
$fs = get_file_storage();
|
||||
foreach (array_keys($files) as $filename) {
|
||||
$filerecord = [
|
||||
'contextid' => \core\context\system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => $filename,
|
||||
];
|
||||
|
||||
$fs->create_file_from_string($filerecord, 'content');
|
||||
}
|
||||
|
||||
// Create and run the upgrade task.
|
||||
$upgardetask = new asynchronous_mimetype_upgrade_task();
|
||||
$upgardetask->set_custom_data([
|
||||
'mimetype' => $mimetype,
|
||||
'extensions' => $extensions,
|
||||
]);
|
||||
ob_start();
|
||||
$upgardetask->execute();
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Check that the task output is correct.
|
||||
foreach ($extensions as $extension) {
|
||||
$this->assertStringContainsString(
|
||||
"Updating mime type for files with extension *.{$extension} to {$mimetype}",
|
||||
$output
|
||||
);
|
||||
$countfiles = count(array_filter(
|
||||
array_keys($files),
|
||||
function ($filename) use ($extension) {
|
||||
return str_ends_with($filename, $extension);
|
||||
}
|
||||
));
|
||||
$this->assertStringContainsString(
|
||||
"Updated {$countfiles} files with extension *.{$extension} to {$mimetype}",
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the mimetype was updated and unrelated files remain untouched.
|
||||
foreach ($files as $filename => $exptectedmimetype) {
|
||||
$mimetypedb = $DB->get_field(
|
||||
table: 'files',
|
||||
return: 'mimetype',
|
||||
conditions: ['filename' => $filename]
|
||||
);
|
||||
$this->assertEquals(expected: $exptectedmimetype, actual: $mimetypedb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2066,3 +2066,32 @@ function upgrade_add_explain_action_to_ai_providers() {
|
||||
|
||||
$currentrecords->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ad-hoc task to upgrade the mime-type of files asynchronously.
|
||||
* Thus, we can considerably reduce the time an upgrade takes.
|
||||
*
|
||||
* @param string $mimetype the desired mime-type
|
||||
* @param string[] $extensions a list of file extensions, without the leading dot
|
||||
* @return void
|
||||
*/
|
||||
function upgrade_create_async_mimetype_upgrade_task(string $mimetype, array $extensions): void {
|
||||
global $DB;
|
||||
|
||||
// Create adhoc task for upgrading of existing files. Due to a code restriction on the upgrade, invoking any core
|
||||
// functions is not permitted. Thus we craft our own ad-hoc task that will process all existing files.
|
||||
$record = new \stdClass();
|
||||
$record->classname = '\core_files\task\asynchronous_mimetype_upgrade_task';
|
||||
$record->component = 'core';
|
||||
$record->customdata = json_encode([
|
||||
'mimetype' => $mimetype,
|
||||
'extensions' => $extensions,
|
||||
]);
|
||||
|
||||
// Next run time based from nextruntime computation in \core\task\manager::queue_adhoc_task().
|
||||
$clock = \core\di::get(\core\clock::class);
|
||||
$nextruntime = $clock->time() - 1;
|
||||
$record->nextruntime = $nextruntime;
|
||||
|
||||
$DB->insert_record('task_adhoc', $record);
|
||||
}
|
||||
|
||||
@@ -540,4 +540,37 @@ final class upgradelib_test extends \advanced_testcase {
|
||||
$this->assertEquals($expected, $block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the upgrade_create_async_mimetype_upgrade_task function performs as expected.
|
||||
*
|
||||
* @covers ::upgrade_create_async_mimetype_upgrade_task
|
||||
*/
|
||||
public function test_upgrade_create_async_mimetype_upgrade_task(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$clock = $this->mock_clock_with_frozen();
|
||||
|
||||
upgrade_create_async_mimetype_upgrade_task('type/subtype', ['extension1', 'extension2']);
|
||||
|
||||
// Ensure that the task was created with correct next runtime.
|
||||
$nextruntime = $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'nextruntime',
|
||||
conditions: ['component' => 'core', 'classname' => '\core_files\task\asynchronous_mimetype_upgrade_task'],
|
||||
strictness: MUST_EXIST,
|
||||
);
|
||||
$this->assertEquals(expected: $clock->time() - 1, actual: $nextruntime);
|
||||
|
||||
// Ensure that the task has the correct custom data.
|
||||
$customdata = $DB->get_field(
|
||||
table: 'task_adhoc',
|
||||
return: 'customdata',
|
||||
conditions: ['component' => 'core', 'classname' => '\core_files\task\asynchronous_mimetype_upgrade_task'],
|
||||
strictness: MUST_EXIST,
|
||||
);
|
||||
$customdata = json_decode($customdata);
|
||||
$this->assertEquals(expected: 'type/subtype', actual: $customdata->mimetype);
|
||||
$this->assertEquals(expected: ['extension1', 'extension2'], actual: $customdata->extensions);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user