205 lines
8.0 KiB
TypeScript

// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreFile, CoreFileProvider } from '@providers/file';
import { CoreSites } from '@providers/sites';
import { CoreTextUtils } from '@providers/utils/text';
import { CoreUtils } from '@providers/utils/utils';
import { CoreH5PCore } from './core';
import { CoreH5PFramework, CoreH5PLibraryDBData } from './framework';
import { CoreH5PMetadata } from './metadata';
import { CoreH5PMainJSONFilesData } from './validator';
/**
* Equivalent to H5P's H5PStorage class.
*/
export class CoreH5PStorage {
constructor(protected h5pCore: CoreH5PCore,
protected h5pFramework: CoreH5PFramework) { }
/**
* Save libraries.
*
* @param librariesJsonData Data about libraries.
* @param folderName Name of the folder of the H5P package.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
protected async saveLibraries(librariesJsonData: any, folderName: string, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.instance.getCurrentSiteId();
// First of all, try to create the dir where the libraries are stored. This way we don't have to do it for each lib.
await CoreFile.instance.createDir(this.h5pCore.h5pFS.getLibrariesFolderPath(siteId));
const libraryIds = [];
// Go through libraries that came with this package.
await Promise.all(Object.keys(librariesJsonData).map(async (libString) => {
const libraryData = librariesJsonData[libString];
// Find local library identifier.
let dbData: CoreH5PLibraryDBData;
try {
dbData = await this.h5pFramework.getLibraryByData(libraryData);
} catch (error) {
// Not found.
}
if (dbData) {
// Library already installed.
libraryData.libraryId = dbData.id;
const isNewPatch = await this.h5pFramework.isPatchedLibrary(libraryData, dbData);
if (!isNewPatch) {
// Same or older version, no need to save.
libraryData.saveDependencies = false;
return;
}
}
libraryData.saveDependencies = true;
// Convert metadataSettings values to boolean and json_encode it before saving.
libraryData.metadataSettings = libraryData.metadataSettings ?
CoreH5PMetadata.boolifyAndEncodeSettings(libraryData.metadataSettings) : null;
// Save the library data in DB.
await this.h5pFramework.saveLibraryData(libraryData, siteId);
// Now save it in FS.
try {
await this.h5pCore.h5pFS.saveLibrary(libraryData, siteId);
} catch (error) {
// An error occurred, delete the DB data because the lib FS data has been deleted.
await this.h5pFramework.deleteLibrary(libraryData.libraryId, siteId);
throw error;
}
if (typeof libraryData.libraryId != 'undefined') {
const promises = [];
// Remove all indexes of contents that use this library.
promises.push(this.h5pCore.h5pFS.deleteContentIndexesForLibrary(libraryData.libraryId, siteId));
if (this.h5pCore.aggregateAssets) {
// Remove cached assets that use this library.
const removedEntries = await this.h5pFramework.deleteCachedAssets(libraryData.libraryId, siteId);
await this.h5pCore.h5pFS.deleteCachedAssets(removedEntries, siteId);
}
await CoreUtils.instance.allPromises(promises);
}
}));
// Go through the libraries again to save dependencies.
await Promise.all(Object.keys(librariesJsonData).map(async (libString) => {
const libraryData = librariesJsonData[libString];
if (!libraryData.saveDependencies) {
return;
}
libraryIds.push(libraryData.libraryId);
// Remove any old dependencies.
await this.h5pFramework.deleteLibraryDependencies(libraryData.libraryId, siteId);
// Insert the different new ones.
const promises = [];
if (typeof libraryData.preloadedDependencies != 'undefined') {
promises.push(this.h5pFramework.saveLibraryDependencies(libraryData.libraryId, libraryData.preloadedDependencies,
'preloaded'));
}
if (typeof libraryData.dynamicDependencies != 'undefined') {
promises.push(this.h5pFramework.saveLibraryDependencies(libraryData.libraryId, libraryData.dynamicDependencies,
'dynamic'));
}
if (typeof libraryData.editorDependencies != 'undefined') {
promises.push(this.h5pFramework.saveLibraryDependencies(libraryData.libraryId, libraryData.editorDependencies,
'editor'));
}
await Promise.all(promises);
}));
// Make sure dependencies, parameter filtering and export files get regenerated for content who uses these libraries.
if (libraryIds.length) {
await this.h5pFramework.clearFilteredParameters(libraryIds, siteId);
}
}
/**
* Save content data in DB and clear cache.
*
* @param content Content to save.
* @param folderName The name of the folder that contains the H5P.
* @param fileUrl The online URL of the package.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the content data.
*/
async savePackage(data: CoreH5PMainJSONFilesData, folderName: string, fileUrl: string, skipContent?: boolean, siteId?: string)
: Promise<any> {
if (this.h5pCore.mayUpdateLibraries()) {
// Save the libraries that were processed.
await this.saveLibraries(data.librariesJsonData, folderName, siteId);
}
const content: any = {};
if (!skipContent) {
// Find main library version.
if (data.mainJsonData.preloadedDependencies) {
const mainLib = data.mainJsonData.preloadedDependencies.find((dependency) => {
return dependency.machineName === data.mainJsonData.mainLibrary;
});
if (mainLib) {
const id = await this.h5pFramework.getLibraryIdByData(mainLib);
mainLib.libraryId = id;
content.library = mainLib;
}
}
content.params = JSON.stringify(data.contentJsonData);
// Save the content data in DB.
await this.h5pCore.saveContent(content, folderName, fileUrl, siteId);
// Save the content files in their right place in FS.
const destFolder = CoreTextUtils.instance.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName);
const contentPath = CoreTextUtils.instance.concatenatePaths(destFolder, 'content');
try {
await this.h5pCore.h5pFS.saveContent(contentPath, folderName, siteId);
} catch (error) {
// An error occurred, delete the DB data because the content files have been deleted.
await this.h5pFramework.deleteContentData(content.id, siteId);
throw error;
}
}
return content;
}
}