MOBILE-3917 h5p: Check missing dependencies

main
Dani Palou 2021-11-16 11:08:51 +01:00
parent 80f1b96f61
commit 1547b66ec9
9 changed files with 123 additions and 15 deletions

View File

@ -246,6 +246,7 @@ function build_lang($lang, $keys) {
}
if ($value->file != 'local_moodlemobileapp') {
$text = str_replace('$a->@', '$a.', $text);
$text = str_replace('$a->', '$a.', $text);
$text = str_replace('{$a', '{{$a', $text);
$text = str_replace('}', '}}', $text);

View File

@ -1749,6 +1749,7 @@
"core.h5p.licensee": "h5p",
"core.h5p.licenseextras": "h5p",
"core.h5p.licenseversion": "h5p",
"core.h5p.missingdependency": "h5p",
"core.h5p.nocopyright": "h5p",
"core.h5p.offlineDialogBody": "h5p",
"core.h5p.offlineDialogHeader": "h5p",

View File

@ -987,6 +987,13 @@ export type CoreH5PLibraryBasicData = {
minorVersion: number; // Minor version.
};
/**
* Data about a missing library.
*/
export type CoreH5PMissingLibrary = CoreH5PLibraryBasicData & {
libString: string; // Library that has the dependency.
};
/**
* Library basic data including patch version.
*/

View File

@ -42,6 +42,7 @@ import { CoreH5PSemantics } from './content-validator';
import { CoreH5PContentBeingSaved, CoreH5PLibraryBeingSaved } from './storage';
import { CoreH5PLibraryAddTo, CoreH5PLibraryMetadataSettings } from './validator';
import { CoreH5PMetadata } from './metadata';
import { Translate } from '@singletons';
/**
* Equivalent to Moodle's implementation of H5PFrameworkInterface.
@ -742,14 +743,14 @@ export class CoreH5PFramework {
/**
* Save what libraries a library is depending on.
*
* @param libraryId Library Id for the library we're saving dependencies for.
* @param library Library data for the library we're saving dependencies for.
* @param dependencies List of dependencies as associative arrays containing machineName, majorVersion, minorVersion.
* @param dependencytype The type of dependency.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
async saveLibraryDependencies(
libraryId: number,
library: CoreH5PLibraryBeingSaved,
dependencies: CoreH5PLibraryBasicData[],
dependencyType: string,
siteId?: string,
@ -761,9 +762,17 @@ export class CoreH5PFramework {
// Get the ID of the library.
const dependencyId = await this.getLibraryIdByData(dependency, siteId);
if (!dependencyId) {
// Missing dependency. It should have been detected before installing the package.
throw new CoreError(Translate.instant('core.h5p.missingdependency', { $a: {
lib: CoreH5PCore.libraryToString(library),
dep: CoreH5PCore.libraryToString(dependency),
} }));
}
// Create the relation.
const entry: Partial<CoreH5PLibraryDependencyDBRecord> = {
libraryid: libraryId,
libraryid: library.libraryId,
requiredlibraryid: dependencyId,
dependencytype: dependencyType,
};

View File

@ -211,7 +211,7 @@ export class CoreH5PHelper {
// Read the contents of the unzipped dir, process them and store them.
const contents = await CoreFile.getDirectoryContents(destFolder);
const filesData = await CoreH5P.h5pValidator.processH5PFiles(destFolder, contents);
const filesData = await CoreH5P.h5pValidator.processH5PFiles(destFolder, contents, siteId);
const content = await CoreH5P.h5pStorage.savePackage(filesData, folderName, fileUrl, false, siteId);

View File

@ -119,24 +119,26 @@ export class CoreH5PStorage {
return;
}
const libId = libraryData.libraryId;
libraryIds.push(libId);
libraryIds.push(libraryData.libraryId);
// Remove any old dependencies.
await this.h5pFramework.deleteLibraryDependencies(libId, siteId);
await this.h5pFramework.deleteLibraryDependencies(libraryData.libraryId, siteId);
// Insert the different new ones.
const promises: Promise<void>[] = [];
if (typeof libraryData.preloadedDependencies != 'undefined') {
promises.push(this.h5pFramework.saveLibraryDependencies(libId, libraryData.preloadedDependencies, 'preloaded'));
promises.push(this.h5pFramework.saveLibraryDependencies(
libraryData,
libraryData.preloadedDependencies,
'preloaded',
));
}
if (typeof libraryData.dynamicDependencies != 'undefined') {
promises.push(this.h5pFramework.saveLibraryDependencies(libId, libraryData.dynamicDependencies, 'dynamic'));
promises.push(this.h5pFramework.saveLibraryDependencies(libraryData, libraryData.dynamicDependencies, 'dynamic'));
}
if (typeof libraryData.editorDependencies != 'undefined') {
promises.push(this.h5pFramework.saveLibraryDependencies(libId, libraryData.editorDependencies, 'editor'));
promises.push(this.h5pFramework.saveLibraryDependencies(libraryData, libraryData.editorDependencies, 'editor'));
}
await Promise.all(promises);

View File

@ -12,17 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreError } from '@classes/errors/error';
import { FileEntry, DirectoryEntry } from '@ionic-native/file/ngx';
import { CoreFile, CoreFileFormat } from '@services/file';
import { CoreTextUtils } from '@services/utils/text';
import { Translate } from '@singletons';
import { CoreH5PSemantics } from './content-validator';
import { CoreH5PCore, CoreH5PLibraryBasicData } from './core';
import { CoreH5PCore, CoreH5PLibraryBasicData, CoreH5PMissingLibrary } from './core';
import { CoreH5PFramework } from './framework';
/**
* Equivalent to H5P's H5PValidator class.
*/
export class CoreH5PValidator {
constructor(public h5pFramework: CoreH5PFramework) {
}
/**
* Get library data.
* This function won't validate most things because it should've been done by the server already.
@ -49,6 +55,57 @@ export class CoreH5PValidator {
return libraryData;
}
/**
* Use the dependency declarations to find any missing libraries.
*
* @param libraries Libraries to check.
* @return Promise resolved with the missing dependencies.
*/
protected getMissingLibraries(libraries: CoreH5PLibrariesJsonData): Record<string, CoreH5PMissingLibrary> {
const missing: Record<string, CoreH5PMissingLibrary> = {};
Object.values(libraries).forEach((library) => {
if (typeof library.preloadedDependencies !== 'undefined') {
Object.assign(missing, this.getMissingDependencies(library.preloadedDependencies, library, libraries));
}
if (typeof library.dynamicDependencies !== 'undefined') {
Object.assign(missing, this.getMissingDependencies(library.dynamicDependencies, library, libraries));
}
if (typeof library.editorDependencies !== 'undefined') {
Object.assign(missing, this.getMissingDependencies(library.editorDependencies, library, libraries));
}
});
return missing;
}
/**
* Helper function for getMissingLibraries, searches for dependency required libraries in the provided list of libraries.
*
* @param dependencies Dependencies to check.
* @param library Library that has these dependencies.
* @param libraries Libraries.
* @return Promise resolved with missing dependencies.
*/
protected getMissingDependencies(
dependencies: CoreH5PLibraryBasicData[],
library: CoreH5PLibraryJsonData,
libraries: CoreH5PLibrariesJsonData,
): Record<string, CoreH5PLibraryBasicData> {
const missing: Record<string, CoreH5PMissingLibrary> = {};
dependencies.forEach((dependency) => {
const libString = CoreH5PCore.libraryToString(dependency);
if (!libraries[libString]) {
missing[libString] = Object.assign(dependency, {
libString: CoreH5PCore.libraryToString(library),
});
}
});
return missing;
}
/**
* Get library data for all libraries in an H5P package.
*
@ -106,9 +163,14 @@ export class CoreH5PValidator {
*
* @param packagePath The path to the package folder.
* @param entries List of files and directories in the root of the package folder.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
async processH5PFiles(packagePath: string, entries: (DirectoryEntry | FileEntry)[]): Promise<CoreH5PMainJSONFilesData> {
async processH5PFiles(
packagePath: string,
entries: (DirectoryEntry | FileEntry)[],
siteId?: string,
): Promise<CoreH5PMainJSONFilesData> {
// Read the needed files.
const results = await Promise.all([
@ -117,6 +179,31 @@ export class CoreH5PValidator {
this.getPackageLibrariesData(packagePath, entries),
]);
// Check if there are missing libraries.
const missingLibraries = this.getMissingLibraries(results[2]);
// Check if the missing libraries are already installed in the app.
await Promise.all(Object.keys(missingLibraries).map(async (libString) => {
const dependency = missingLibraries[libString];
const dependencyId = await this.h5pFramework.getLibraryIdByData(dependency, siteId);
if (dependencyId) {
// Lib is installed.
delete missingLibraries[libString];
}
}));
if (Object.keys(missingLibraries).length > 0) {
// Missing library, throw error.
const libString = Object.keys(missingLibraries)[0];
const missingLibrary = missingLibraries[libString];
throw new CoreError(Translate.instant('core.h5p.missingdependency', { $a: {
lib: missingLibrary.libString,
dep: libString,
} }));
}
return {
librariesJsonData: results[2],
mainJsonData: results[0],

View File

@ -59,6 +59,7 @@
"licensee": "Licensee",
"licenseextras": "Licence extras",
"licenseversion": "Licence version",
"missingdependency": "Missing dependency {{$a.dep}} required by {{$a.lib}}.",
"nocopyright": "No copyright information available for this content.",
"offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.",
"offlineDialogHeader": "Your connection to the server was lost",
@ -90,4 +91,4 @@
"years": "Year(s)",
"yearsfrom": "Years (from)",
"yearsto": "Years (to)"
}
}

View File

@ -48,8 +48,8 @@ export class CoreH5PProvider {
constructor() {
this.queueRunner = new CoreQueueRunner(1);
this.h5pValidator = new CoreH5PValidator();
this.h5pFramework = new CoreH5PFramework();
this.h5pValidator = new CoreH5PValidator(this.h5pFramework);
this.h5pCore = new CoreH5PCore(this.h5pFramework);
this.h5pStorage = new CoreH5PStorage(this.h5pCore, this.h5pFramework);
this.h5pPlayer = new CoreH5PPlayer(this.h5pCore, this.h5pStorage);