MOBILE-3917 h5p: Check missing dependencies
This commit is contained in:
		
							parent
							
								
									80f1b96f61
								
							
						
					
					
						commit
						1547b66ec9
					
				| @ -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); | ||||
|  | ||||
| @ -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", | ||||
|  | ||||
| @ -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. | ||||
|  */ | ||||
|  | ||||
| @ -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, | ||||
|             }; | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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], | ||||
|  | ||||
| @ -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)" | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user