MOBILE-2235 h5p: Install H5P libraries and save content
parent
b9850b08dc
commit
93259097d6
|
@ -148,6 +148,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.observer = this.eventsProvider.on(eventName, () => {
|
||||
this.calculateState();
|
||||
});
|
||||
}).catch(() => {
|
||||
// An error probably means the file cannot be downloaded or we cannot check it (offline).
|
||||
});
|
||||
|
||||
return;
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CoreH5PComponentsModule } from './components/components.module';
|
||||
import { CoreH5PProvider } from './providers/h5p';
|
||||
import { CoreH5PUtilsProvider } from './providers/utils';
|
||||
import { CoreH5PPluginFileHandler } from './providers/pluginfile-handler';
|
||||
import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const CORE_H5P_PROVIDERS: any[] = [
|
||||
CoreH5PProvider
|
||||
CoreH5PProvider,
|
||||
CoreH5PUtilsProvider
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -30,6 +32,7 @@ export const CORE_H5P_PROVIDERS: any[] = [
|
|||
],
|
||||
providers: [
|
||||
CoreH5PProvider,
|
||||
CoreH5PUtilsProvider,
|
||||
CoreH5PPluginFileHandler
|
||||
],
|
||||
exports: []
|
||||
|
|
|
@ -13,11 +13,15 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreH5PUtilsProvider } from './utils';
|
||||
|
||||
/**
|
||||
* Service to provide H5P functionalities.
|
||||
|
@ -25,15 +29,179 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
|
|||
@Injectable()
|
||||
export class CoreH5PProvider {
|
||||
|
||||
protected CONTENT_TABLE = 'h5p_content'; // H5P content.
|
||||
protected LIBRARIES_TABLE = 'h5p_libraries'; // Installed libraries.
|
||||
protected LIBRARY_DEPENDENCIES_TABLE = 'h5p_library_dependencies'; // Library dependencies.
|
||||
|
||||
protected siteSchema: CoreSiteSchema = {
|
||||
name: 'CoreH5PProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: this.CONTENT_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
{
|
||||
name: 'jsoncontent',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'mainlibraryid',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'displayoptions',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'foldername',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'filtered',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: this.LIBRARIES_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
{
|
||||
name: 'machinename',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'majorversion',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'minorversion',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'patchversion',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'runnable',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'fullscreen',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'embedtypes',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'preloadedjs',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'preloadedcss',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'droplibrarycss',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'semantics',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'addto',
|
||||
type: 'TEXT'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: this.LIBRARY_DEPENDENCIES_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
{
|
||||
name: 'libraryid',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'requiredlibraryid',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
},
|
||||
{
|
||||
name: 'dependencytype',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
protected ROOT_CACHE_KEY = 'mmH5P:';
|
||||
|
||||
protected logger;
|
||||
|
||||
constructor(logger: CoreLoggerProvider,
|
||||
eventsProvider: CoreEventsProvider,
|
||||
private sitesProvider: CoreSitesProvider,
|
||||
private textUtils: CoreTextUtilsProvider) {
|
||||
private textUtils: CoreTextUtilsProvider,
|
||||
private fileProvider: CoreFileProvider,
|
||||
private mimeUtils: CoreMimetypeUtilsProvider,
|
||||
private h5pUtils: CoreH5PUtilsProvider) {
|
||||
|
||||
this.logger = logger.getInstance('CoreFilterProvider');
|
||||
this.logger = logger.getInstance('CoreH5PProvider');
|
||||
|
||||
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.SITE_STORAGE_DELETED, (data) => {
|
||||
this.deleteAllData(data.siteId).catch((error) => {
|
||||
this.logger.error('Error deleting all H5P data from site.', error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,7 +222,7 @@ export class CoreH5PProvider {
|
|||
*
|
||||
* @param site Site. If not defined, current site.
|
||||
* @return Promise resolved with true if ws is available, false otherwise.
|
||||
* @since 3.4
|
||||
* @since 3.8
|
||||
*/
|
||||
canGetTrustedH5PFileInSite(site?: CoreSite): boolean {
|
||||
site = site || this.sitesProvider.getCurrentSite();
|
||||
|
@ -62,6 +230,346 @@ export class CoreH5PProvider {
|
|||
return site.wsAvailable('core_h5p_get_trusted_h5p_file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Will clear filtered params for all the content that uses the specified libraries.
|
||||
* This means that the content dependencies will have to be rebuilt and the parameters re-filtered.
|
||||
*
|
||||
* @param libraryIds Array of library ids.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected clearFilteredParameters(libraryIds: number[], siteId?: string): Promise<any> {
|
||||
|
||||
if (!libraryIds || !libraryIds.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
const whereAndParams = db.getInOrEqual(libraryIds);
|
||||
whereAndParams[0] = 'mainlibraryid ' + whereAndParams[0];
|
||||
|
||||
return db.updateRecordsWhere(this.CONTENT_TABLE, { filtered: null }, whereAndParams[0], whereAndParams[1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the H5P data from the DB of a certain site.
|
||||
*
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected deleteAllData(siteId: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return Promise.all([
|
||||
db.deleteRecords(this.CONTENT_TABLE),
|
||||
db.deleteRecords(this.LIBRARIES_TABLE),
|
||||
db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE)
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete content data from DB.
|
||||
*
|
||||
* @param id Content ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
deleteContentData(id: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.deleteRecords(this.CONTENT_TABLE, {id: id});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete library data from DB.
|
||||
*
|
||||
* @param id Library ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
deleteLibraryData(id: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.deleteRecords(this.LIBRARIES_TABLE, {id: id});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all dependencies belonging to given library.
|
||||
*
|
||||
* @param libraryId Library ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
deleteLibraryDependencies(libraryId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE, {libraryid: libraryId});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a library from the file system.
|
||||
*
|
||||
* @param libraryData The library data.
|
||||
* @param folderName Folder name. If not provided, it will be calculated.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
deleteLibraryFolder(libraryData: any, folderName?: string, siteId?: string): Promise<any> {
|
||||
return this.fileProvider.removeDir(this.getLibraryFolderPath(libraryData, siteId, folderName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an H5P file. Some of this code was copied from the isValidPackage function in Moodle's H5PValidator.
|
||||
* This function won't validate most things because it should've been done by the server already.
|
||||
*
|
||||
* @param fileUrl The file URL used to download the file.
|
||||
* @param file The file entry of the downloaded file.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
extractH5PFile(fileUrl: string, file: FileEntry, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Unzip the file.
|
||||
const folderName = this.mimeUtils.removeExtension(file.name),
|
||||
destFolder = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName);
|
||||
|
||||
// Make sure the dest dir doesn't exist already.
|
||||
return this.fileProvider.removeDir(destFolder).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return this.fileProvider.createDir(destFolder);
|
||||
}).then(() => {
|
||||
return this.fileProvider.unzipFile(file.toURL(), destFolder);
|
||||
}).then(() => {
|
||||
// Read the contents of the unzipped dir.
|
||||
return this.fileProvider.getDirectoryContents(destFolder);
|
||||
}).then((contents) => {
|
||||
return this.processH5PFiles(destFolder, contents).then((data) => {
|
||||
const content: any = {};
|
||||
|
||||
// Save the libraries that were processed.
|
||||
return this.saveLibraries(data.librariesJsonData, siteId).then(() => {
|
||||
// Now treat contents.
|
||||
|
||||
// Find main library version
|
||||
for (const i in data.mainJsonData.preloadedDependencies) {
|
||||
const dependency = data.mainJsonData.preloadedDependencies[i];
|
||||
|
||||
if (dependency.machineName === data.mainJsonData.mainLibrary) {
|
||||
return this.getLibraryIdByData(dependency).then((id) => {
|
||||
dependency.libraryId = id;
|
||||
content.library = dependency;
|
||||
});
|
||||
}
|
||||
}
|
||||
}).then(() => {
|
||||
// Save the content data in DB.
|
||||
content.params = JSON.stringify(data.contentJsonData);
|
||||
|
||||
return this.saveContentData(content, folderName, siteId);
|
||||
}).then(() => {
|
||||
// Save the content files in their right place.
|
||||
const contentPath = this.textUtils.concatenatePaths(destFolder, 'content');
|
||||
|
||||
return this.saveContentInFS(contentPath, folderName, siteId).catch((error) => {
|
||||
// An error occurred, delete the DB data because the content data has been deleted.
|
||||
return this.deleteContentData(content.id, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
// Remove tmp folder.
|
||||
return this.fileProvider.removeDir(destFolder).catch(() => {
|
||||
// Ignore errors, it will be deleted eventually.
|
||||
});
|
||||
});
|
||||
|
||||
// @todo: Load content? It's done in the player construct.
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a package content path.
|
||||
*
|
||||
* @param folderName Name of the folder of the H5P package.
|
||||
* @param siteId The site ID.
|
||||
* @return Folder path.
|
||||
*/
|
||||
getContentFolderPath(folderName: string, siteId: string): string {
|
||||
return this.textUtils.concatenatePaths(this.fileProvider.getSiteFolder(siteId), 'h5p/packages/' + folderName + '/content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get library data. This code is based on the getLibraryData from Moodle's H5PValidator.
|
||||
* This function won't validate most things because it should've been done by the server already.
|
||||
*
|
||||
* @param libDir Directory where the library files are.
|
||||
* @param libPath Path to the directory where the library files are.
|
||||
* @param h5pDir Path to the directory where this h5p files are.
|
||||
* @return Library data.
|
||||
*/
|
||||
protected getLibraryData(libDir: DirectoryEntry, libPath: string, h5pDir: string): any {
|
||||
const libraryJsonPath = this.textUtils.concatenatePaths(libPath, 'library.json'),
|
||||
semanticsPath = this.textUtils.concatenatePaths(libPath, 'semantics.json'),
|
||||
langPath = this.textUtils.concatenatePaths(libPath, 'language'),
|
||||
iconPath = this.textUtils.concatenatePaths(libPath, 'icon.svg'),
|
||||
promises = [];
|
||||
let h5pData,
|
||||
semanticsData,
|
||||
langData,
|
||||
hasIcon;
|
||||
|
||||
// Read the library json file.
|
||||
promises.push(this.fileProvider.readFile(libraryJsonPath, CoreFileProvider.FORMATJSON).then((data) => {
|
||||
h5pData = data;
|
||||
}));
|
||||
|
||||
// Get library semantics if it exists.
|
||||
promises.push(this.fileProvider.readFile(semanticsPath, CoreFileProvider.FORMATJSON).then((data) => {
|
||||
semanticsData = data;
|
||||
}).catch(() => {
|
||||
// Probably doesn't exist, ignore.
|
||||
}));
|
||||
|
||||
// Get language data if it exists.
|
||||
promises.push(this.fileProvider.getDirectoryContents(langPath).then((entries) => {
|
||||
const subPromises = [];
|
||||
langData = {};
|
||||
|
||||
entries.forEach((entry) => {
|
||||
const langFilePath = this.textUtils.concatenatePaths(langPath, entry.name);
|
||||
|
||||
subPromises.push(this.fileProvider.readFile(langFilePath, CoreFileProvider.FORMATJSON).then((data) => {
|
||||
const parts = entry.name.split('.'); // The language code is in parts[0].
|
||||
langData[parts[0]] = data;
|
||||
}));
|
||||
});
|
||||
}).catch(() => {
|
||||
// Probably doesn't exist, ignore.
|
||||
}));
|
||||
|
||||
// Check if it has icon.
|
||||
promises.push(this.fileProvider.getFile(iconPath).then(() => {
|
||||
hasIcon = true;
|
||||
}).catch(() => {
|
||||
hasIcon = false;
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
h5pData.semantics = semanticsData;
|
||||
h5pData.language = langData;
|
||||
h5pData.hasIcon = hasIcon;
|
||||
|
||||
return h5pData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a library data stored in DB.
|
||||
*
|
||||
* @param machineName Machine name.
|
||||
* @param majorVersion Major version number.
|
||||
* @param minorVersion Minor version number.
|
||||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Promise resolved with the library data, rejected if not found.
|
||||
*/
|
||||
protected getLibrary(machineName: string, majorVersion?: string | number, minorVersion?: string | number, siteId?: string)
|
||||
: Promise<CoreH5PLibraryDBData> {
|
||||
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
const conditions: any = {
|
||||
machinename: machineName
|
||||
};
|
||||
|
||||
if (typeof majorVersion != 'undefined') {
|
||||
conditions.majorversion = majorVersion;
|
||||
}
|
||||
if (typeof minorVersion != 'undefined') {
|
||||
conditions.minorversion = minorVersion;
|
||||
}
|
||||
|
||||
return db.getRecords(this.LIBRARIES_TABLE, conditions);
|
||||
}).then((libraries) => {
|
||||
if (!libraries.length) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return libraries[0];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a library data stored in DB.
|
||||
*
|
||||
* @param libraryData Library data.
|
||||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Promise resolved with the library data, rejected if not found.
|
||||
*/
|
||||
protected getLibraryByData(libraryData: any, siteId?: string): Promise<CoreH5PLibraryDBData> {
|
||||
return this.getLibrary(libraryData.machineName, libraryData.majorVersion, libraryData.minorVersion, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a library ID. If not found, return null.
|
||||
*
|
||||
* @param machineName Machine name.
|
||||
* @param majorVersion Major version number.
|
||||
* @param minorVersion Minor version number.
|
||||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Promise resolved with the library ID, null if not found.
|
||||
*/
|
||||
protected getLibraryId(machineName: string, majorVersion?: string | number, minorVersion?: string | number, siteId?: string)
|
||||
: Promise<number> {
|
||||
|
||||
return this.getLibrary(machineName, majorVersion, minorVersion, siteId).then((library) => {
|
||||
return (library && library.id) || null;
|
||||
}).catch(() => {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a library ID. If not found, return null.
|
||||
*
|
||||
* @param libraryData Library data.
|
||||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Promise resolved with the library ID, null if not found.
|
||||
*/
|
||||
protected getLibraryIdByData(libraryData: any, siteId?: string): Promise<number> {
|
||||
return this.getLibraryId(libraryData.machineName, libraryData.majorVersion, libraryData.minorVersion, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get libraries folder path.
|
||||
*
|
||||
* @param siteId The site ID.
|
||||
* @return Folder path.
|
||||
*/
|
||||
getLibrariesFolderPath(siteId: string): string {
|
||||
return this.textUtils.concatenatePaths(this.fileProvider.getSiteFolder(siteId), 'h5p/lib');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a library's folder path.
|
||||
*
|
||||
* @param libraryData The library data.
|
||||
* @param siteId The site ID.
|
||||
* @param folderName Folder name. If not provided, it will be calculated.
|
||||
* @return Folder path.
|
||||
*/
|
||||
getLibraryFolderPath(libraryData: any, siteId: string, folderName?: string): string {
|
||||
if (!folderName) {
|
||||
folderName = this.libraryToString(libraryData, true);
|
||||
}
|
||||
|
||||
return this.textUtils.concatenatePaths(this.getLibrariesFolderPath(siteId), folderName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a trusted H5P file.
|
||||
*
|
||||
|
@ -128,21 +636,6 @@ export class CoreH5PProvider {
|
|||
return this.ROOT_CACHE_KEY + 'trustedH5PFile:';
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat an H5P url before sending it to WS.
|
||||
*
|
||||
* @param url H5P file URL.
|
||||
* @param siteUrl Site URL.
|
||||
* @return Treated url.
|
||||
*/
|
||||
protected treatH5PUrl(url: string, siteUrl: string): string {
|
||||
if (url.indexOf(this.textUtils.concatenatePaths(siteUrl, '/webservice/pluginfile.php')) === 0) {
|
||||
url = url.replace('/webservice/pluginfile', '/pluginfile');
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all trusted H5P file WS calls.
|
||||
*
|
||||
|
@ -167,6 +660,362 @@ export class CoreH5PProvider {
|
|||
return site.invalidateWsCacheForKey(this.getTrustedH5PFileCacheKey(url));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes library data as string on the form {machineName} {majorVersion}.{minorVersion}.
|
||||
*
|
||||
* @param libraryData Library data.
|
||||
* @param folderName Use hyphen instead of space in returned string.
|
||||
* @return String on the form {machineName} {majorVersion}.{minorVersion}.
|
||||
*/
|
||||
protected libraryToString(libraryData: any, folderName?: boolean): string {
|
||||
return (libraryData.machineName ? libraryData.machineName : libraryData.name) + (folderName ? '-' : ' ') +
|
||||
libraryData.majorVersion + '.' + libraryData.minorVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process libraries from an H5P library, getting the required data to save them.
|
||||
* This code was copied from the isValidPackage function in Moodle's H5PValidator.
|
||||
* This function won't validate most things because it should've been done by the server already.
|
||||
*
|
||||
* @param fileUrl The file URL used to download the file.
|
||||
* @param file The file entry of the downloaded file.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected processH5PFiles(destFolder: string, entries: (DirectoryEntry | FileEntry)[])
|
||||
: Promise<{librariesJsonData: any, mainJsonData: any, contentJsonData: any}> {
|
||||
|
||||
const promises = [],
|
||||
libraries: any = {};
|
||||
let contentJsonData,
|
||||
mainH5PData;
|
||||
|
||||
// Read the h5p.json file.
|
||||
const h5pJsonPath = this.textUtils.concatenatePaths(destFolder, 'h5p.json');
|
||||
promises.push(this.fileProvider.readFile(h5pJsonPath, CoreFileProvider.FORMATJSON).then((data) => {
|
||||
mainH5PData = data;
|
||||
}));
|
||||
|
||||
// Read the content.json file.
|
||||
const contentJsonPath = this.textUtils.concatenatePaths(destFolder, 'content/content.json');
|
||||
promises.push(this.fileProvider.readFile(contentJsonPath, CoreFileProvider.FORMATJSON).then((data) => {
|
||||
contentJsonData = data;
|
||||
}));
|
||||
|
||||
// Treat libraries.
|
||||
entries.forEach((entry) => {
|
||||
if (entry.name[0] == '.' || entry.name[0] == '_' || entry.name == 'content' || entry.isFile) {
|
||||
// Skip files, the content folder and any folder starting with a . or _.
|
||||
return;
|
||||
}
|
||||
|
||||
const libDirPath = this.textUtils.concatenatePaths(destFolder, entry.name);
|
||||
|
||||
promises.push(this.getLibraryData(<DirectoryEntry> entry, libDirPath, destFolder).then((libraryH5PData) => {
|
||||
libraryH5PData.uploadDirectory = libDirPath;
|
||||
libraries[this.libraryToString(libraryH5PData)] = libraryH5PData;
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return {
|
||||
librariesJsonData: libraries,
|
||||
mainJsonData: mainH5PData,
|
||||
contentJsonData: contentJsonData
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save content data in DB and clear cache.
|
||||
*
|
||||
* @param content Content to save.
|
||||
* @param folderName The name of the folder that contains the H5P.
|
||||
* @return Promise resolved with content ID.
|
||||
*/
|
||||
protected saveContentData(content: any, folderName: string, siteId?: string): Promise<number> {
|
||||
// Save in DB.
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
|
||||
const data: any = {
|
||||
jsoncontent: content.params,
|
||||
displayoptions: content.disable,
|
||||
mainlibraryid: content.library.libraryId,
|
||||
timemodified: Date.now(),
|
||||
filtered: null,
|
||||
foldername: folderName
|
||||
};
|
||||
|
||||
if (typeof content.id != 'undefined') {
|
||||
data.id = content.id;
|
||||
} else {
|
||||
data.timecreated = data.timemodified;
|
||||
}
|
||||
|
||||
return db.insertRecord(this.CONTENT_TABLE, data).then(() => {
|
||||
if (!data.id) {
|
||||
// New content. Get its ID.
|
||||
return db.getRecord(this.CONTENT_TABLE, data).then((entry) => {
|
||||
content.id = entry.id;
|
||||
});
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
// If resetContentUserData is implemented in the future, it should be called in here.
|
||||
return content.id;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the content in filesystem.
|
||||
*
|
||||
* @param contentPath Path to the current content folder (tmp).
|
||||
* @param folderName Name to put to the content folder.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected saveContentInFS(contentPath: string, folderName: string, siteId: string): Promise<any> {
|
||||
const folderPath = this.getContentFolderPath(folderName, siteId);
|
||||
|
||||
// Delete existing content for this package.
|
||||
return this.fileProvider.removeDir(folderPath).catch(() => {
|
||||
// Ignore errors, maybe it doesn't exist.
|
||||
}).then(() => {
|
||||
// Copy the new one.
|
||||
return this.fileProvider.moveDir(contentPath, folderPath);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save libraries. This code is based on the saveLibraries function from Moodle's H5PStorage.
|
||||
*
|
||||
* @param librariesJsonData Data about libraries.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected saveLibraries(librariesJsonData: any, siteId?: string): Promise<any> {
|
||||
const libraryIds = [];
|
||||
|
||||
// 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.
|
||||
return this.fileProvider.createDir(this.getLibrariesFolderPath(siteId)).then(() => {
|
||||
const promises = [];
|
||||
|
||||
// Go through libraries that came with this package.
|
||||
for (const libString in librariesJsonData) {
|
||||
const libraryData = librariesJsonData[libString];
|
||||
|
||||
// Find local library identifier
|
||||
promises.push(this.getLibraryByData(libraryData).catch(() => {
|
||||
// Not found.
|
||||
}).then((dbData) => {
|
||||
if (dbData) {
|
||||
// Library already installed.
|
||||
libraryData.libraryId = dbData.id;
|
||||
|
||||
if (libraryData.patchVersion <= dbData.patchversion) {
|
||||
// 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 ?
|
||||
this.h5pUtils.boolifyAndEncodeMetadataSettings(libraryData.metadataSettings) : null;
|
||||
|
||||
// Save the library data in DB.
|
||||
return this.saveLibraryData(libraryData, siteId).then(() => {
|
||||
// Now save it in FS.
|
||||
return this.saveLibraryInFS(libraryData, siteId).catch((error) => {
|
||||
// An error occurred, delete the DB data because the lib FS data has been deleted.
|
||||
return this.deleteLibraryData(libraryData.libraryId, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
// @todo: Remove cached asses that use this library.
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(() => {
|
||||
// Go through the libraries again to save dependencies.
|
||||
const promises = [];
|
||||
|
||||
for (const libString in librariesJsonData) {
|
||||
const libraryData = librariesJsonData[libString];
|
||||
if (!libraryData.saveDependencies) {
|
||||
continue;
|
||||
}
|
||||
|
||||
libraryIds.push(libraryData.libraryId);
|
||||
|
||||
// Remove any old dependencies.
|
||||
promises.push(this.deleteLibraryDependencies(libraryData.libraryId).then(() => {
|
||||
// Insert the different new ones.
|
||||
const subPromises = [];
|
||||
|
||||
if (typeof libraryData.preloadedDependencies != 'undefined') {
|
||||
subPromises.push(this.saveLibraryDependencies(libraryData.libraryId, libraryData.preloadedDependencies,
|
||||
'preloaded'));
|
||||
}
|
||||
if (typeof libraryData.dynamicDependencies != 'undefined') {
|
||||
subPromises.push(this.saveLibraryDependencies(libraryData.libraryId, libraryData.dynamicDependencies,
|
||||
'dynamic'));
|
||||
}
|
||||
if (typeof libraryData.editorDependencies != 'undefined') {
|
||||
subPromises.push(this.saveLibraryDependencies(libraryData.libraryId, libraryData.editorDependencies,
|
||||
'editor'));
|
||||
}
|
||||
|
||||
return Promise.all(subPromises);
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(() => {
|
||||
// Make sure dependencies, parameter filtering and export files get regenerated for content who uses these libraries.
|
||||
if (libraryIds.length) {
|
||||
return this.clearFilteredParameters(libraryIds, siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a library in filesystem.
|
||||
*
|
||||
* @param libraryData Library data.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected saveLibraryInFS(libraryData: any, siteId?: string): Promise<any> {
|
||||
const folderPath = this.getLibraryFolderPath(libraryData, siteId);
|
||||
|
||||
// Delete existing library version.
|
||||
return this.fileProvider.removeDir(folderPath).catch(() => {
|
||||
// Ignore errors, maybe it doesn't exist.
|
||||
}).then(() => {
|
||||
// Copy the new one.
|
||||
return this.fileProvider.moveDir(libraryData.uploadDirectory, folderPath, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save library data in DB.
|
||||
*
|
||||
* @param libraryData Library data to save.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected saveLibraryData(libraryData: any, siteId?: string): Promise<any> {
|
||||
// Some special properties needs some checking and converting before they can be saved.
|
||||
const preloadedJS = this.h5pUtils.libraryParameterValuesToCsv(libraryData, 'preloadedJs', 'path'),
|
||||
preloadedCSS = this.h5pUtils.libraryParameterValuesToCsv(libraryData, 'preloadedCss', 'path'),
|
||||
dropLibraryCSS = this.h5pUtils.libraryParameterValuesToCsv(libraryData, 'dropLibraryCss', 'machineName');
|
||||
|
||||
if (typeof libraryData.semantics == 'undefined') {
|
||||
libraryData.semantics = '';
|
||||
}
|
||||
if (typeof libraryData.fullscreen == 'undefined') {
|
||||
libraryData.fullscreen = 0;
|
||||
}
|
||||
|
||||
let embedTypes = '';
|
||||
if (typeof libraryData.embedTypes != 'undefined') {
|
||||
embedTypes = libraryData.embedTypes.join(', ');
|
||||
}
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const db = site.getDb(),
|
||||
data: any = {
|
||||
title: libraryData.title,
|
||||
machinename: libraryData.machineName,
|
||||
majorversion: libraryData.majorVersion,
|
||||
minorversion: libraryData.minorVersion,
|
||||
patchversion: libraryData.patchVersion,
|
||||
runnable: libraryData.runnable,
|
||||
fullscreen: libraryData.fullscreen,
|
||||
embedtypes: embedTypes,
|
||||
preloadedjs: preloadedJS,
|
||||
preloadedcss: preloadedCSS,
|
||||
droplibrarycss: dropLibraryCSS,
|
||||
semantics: libraryData.semantics,
|
||||
addto: typeof libraryData.addTo != 'undefined' ? JSON.stringify(libraryData.addTo) : null,
|
||||
};
|
||||
|
||||
if (libraryData.libraryId) {
|
||||
data.id = libraryData.libraryId;
|
||||
}
|
||||
|
||||
return db.insertRecord(this.LIBRARIES_TABLE, data).then(() => {
|
||||
if (!data.id) {
|
||||
// New library. Get its ID.
|
||||
return db.getRecord(this.LIBRARIES_TABLE, data).then((entry) => {
|
||||
libraryData.libraryId = entry.id;
|
||||
});
|
||||
} else {
|
||||
// Updated libary. Remove old dependencies.
|
||||
return this.deleteLibraryDependencies(data.id, site.getId());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save what libraries a library is depending on.
|
||||
*
|
||||
* @param libraryId Library Id 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.
|
||||
*/
|
||||
protected saveLibraryDependencies(libraryId: number, dependencies: any[], dependencyType: string, siteId?: string)
|
||||
: Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
|
||||
const promises = [];
|
||||
|
||||
dependencies.forEach((dependency) => {
|
||||
// Get the ID of the library.
|
||||
promises.push(this.getLibraryIdByData(dependency, siteId).then((dependencyId) => {
|
||||
// Create the relation.
|
||||
const entry = {
|
||||
libraryid: libraryId,
|
||||
requiredlibraryid: dependencyId,
|
||||
dependencytype: dependencyType
|
||||
};
|
||||
|
||||
return db.insertRecord(this.LIBRARY_DEPENDENCIES_TABLE, entry);
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat an H5P url before sending it to WS.
|
||||
*
|
||||
* @param url H5P file URL.
|
||||
* @param siteUrl Site URL.
|
||||
* @return Treated url.
|
||||
*/
|
||||
protected treatH5PUrl(url: string, siteUrl: string): string {
|
||||
if (url.indexOf(this.textUtils.concatenatePaths(siteUrl, '/webservice/pluginfile.php')) === 0) {
|
||||
url = url.replace('/webservice/pluginfile', '/pluginfile');
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -186,3 +1035,47 @@ export type CoreH5PGetTrustedH5PFileResult = {
|
|||
files: CoreWSExternalFile[]; // Files.
|
||||
warnings: CoreWSExternalWarning[]; // List of warnings.
|
||||
};
|
||||
|
||||
/**
|
||||
* Content data stored in DB.
|
||||
*/
|
||||
export type CoreH5PContentDBData = {
|
||||
id: number; // The id of the content.
|
||||
jsoncontent: string; // The content in json format.
|
||||
mainlibraryid: number; // The library we first instantiate for this node.
|
||||
displayoptions: number; // H5P Button display options.
|
||||
foldername: string; // Name of the folder that contains the contents.
|
||||
filtered: string; // Filtered version of json_content.
|
||||
timecreated: number; // Time created.
|
||||
timemodified: number; // Time modified.
|
||||
};
|
||||
|
||||
/**
|
||||
* Library data stored in DB.
|
||||
*/
|
||||
export type CoreH5PLibraryDBData = {
|
||||
id: number; // The id of the library.
|
||||
machinename: string; // The library machine name.
|
||||
title: string; // The human readable name of this library.
|
||||
majorversion: number; // Major version.
|
||||
minorversion: number; // Minor version.
|
||||
patchversion: number; // Patch version.
|
||||
runnable: number; // Can this library be started by the module? I.e. not a dependency.
|
||||
fullscreen: number; // Display fullscreen button.
|
||||
embedtypes: string; // List of supported embed types.
|
||||
preloadedjs?: string; // Comma separated list of scripts to load.
|
||||
preloadedcss?: string; // Comma separated list of stylesheets to load.
|
||||
droplibrarycss?: string; // List of libraries that should not have CSS included if this library is used. Comma separated list.
|
||||
semantics?: string; // The semantics definition in json format.
|
||||
addto?: string; // Plugin configuration data.
|
||||
};
|
||||
|
||||
/**
|
||||
* Library dependencies stored in DB.
|
||||
*/
|
||||
export type CoreH5PLibraryDependenciesDBData = {
|
||||
id: number; // Id.
|
||||
libraryid: number; // The id of an H5P library.
|
||||
requiredlibraryid: number; // The dependent library to load.
|
||||
dependencytype: string; // Type: preloaded, dynamic, or editor.
|
||||
};
|
||||
|
|
|
@ -108,14 +108,6 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise<any> {
|
||||
// Unzip the file.
|
||||
const destFolder = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER,
|
||||
'h5p/' + this.mimeUtils.removeExtension(file.name));
|
||||
|
||||
return this.fileProvider.createDir(destFolder).then(() => {
|
||||
return this.fileProvider.unzipFile(file.toURL(), destFolder);
|
||||
}).then(() => {
|
||||
// @todo: Deploy the package.
|
||||
});
|
||||
return this.h5pProvider.extractH5PFile(fileUrl, file, siteId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Utils service with helper functions for H5P.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreH5PUtilsProvider {
|
||||
|
||||
constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* The metadataSettings field in libraryJson uses 1 for true and 0 for false.
|
||||
* Here we are converting these to booleans, and also doing JSON encoding.
|
||||
*
|
||||
* @param metadataSettings Settings.
|
||||
* @return Stringified settings.
|
||||
*/
|
||||
boolifyAndEncodeMetadataSettings(metadataSettings: any): string {
|
||||
// Convert metadataSettings values to boolean.
|
||||
if (typeof metadataSettings.disable != 'undefined') {
|
||||
metadataSettings.disable = metadataSettings.disable === 1;
|
||||
}
|
||||
if (typeof metadataSettings.disableExtraTitleField != 'undefined') {
|
||||
metadataSettings.disableExtraTitleField = metadataSettings.disableExtraTitleField === 1;
|
||||
}
|
||||
|
||||
return JSON.stringify(metadataSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert list of library parameter values to csv.
|
||||
*
|
||||
* @param libraryData Library data as found in library.json files.
|
||||
* @param key Key that should be found in libraryData.
|
||||
* @param searchParam The library parameter (Default: 'path').
|
||||
* @return Library parameter values separated by ', '
|
||||
*/
|
||||
libraryParameterValuesToCsv(libraryData: any, key: string, searchParam: string = 'path'): string {
|
||||
if (typeof libraryData[key] != 'undefined') {
|
||||
const parameterValues = [];
|
||||
|
||||
libraryData[key].forEach((file) => {
|
||||
for (const index in file) {
|
||||
if (index === searchParam) {
|
||||
parameterValues.push(file[index]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return parameterValues.join(',');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ export class CoreFileProvider {
|
|||
static FORMATDATAURL = 1;
|
||||
static FORMATBINARYSTRING = 2;
|
||||
static FORMATARRAYBUFFER = 3;
|
||||
static FORMATJSON = 4;
|
||||
|
||||
// Folders.
|
||||
static SITESFOLDER = 'sites';
|
||||
|
@ -491,6 +492,7 @@ export class CoreFileProvider {
|
|||
* FORMATDATAURL
|
||||
* FORMATBINARYSTRING
|
||||
* FORMATARRAYBUFFER
|
||||
* FORMATJSON
|
||||
* @return Promise to be resolved when the file is read.
|
||||
*/
|
||||
readFile(path: string, format: number = CoreFileProvider.FORMATTEXT): Promise<any> {
|
||||
|
@ -505,6 +507,16 @@ export class CoreFileProvider {
|
|||
return this.file.readAsBinaryString(this.basePath, path);
|
||||
case CoreFileProvider.FORMATARRAYBUFFER:
|
||||
return this.file.readAsArrayBuffer(this.basePath, path);
|
||||
case CoreFileProvider.FORMATJSON:
|
||||
return this.file.readAsText(this.basePath, path).then((text) => {
|
||||
const parsed = this.textUtils.parseJSON(text, null);
|
||||
|
||||
if (parsed == null && text != null) {
|
||||
return Promise.reject('Error parsing JSON file: ' + path);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
});
|
||||
default:
|
||||
return this.file.readAsText(this.basePath, path);
|
||||
}
|
||||
|
@ -519,6 +531,7 @@ export class CoreFileProvider {
|
|||
* FORMATDATAURL
|
||||
* FORMATBINARYSTRING
|
||||
* FORMATARRAYBUFFER
|
||||
* FORMATJSON
|
||||
* @return Promise to be resolved when the file is read.
|
||||
*/
|
||||
readFileData(fileData: any, format: number = CoreFileProvider.FORMATTEXT): Promise<any> {
|
||||
|
@ -531,7 +544,18 @@ export class CoreFileProvider {
|
|||
reader.onloadend = (evt): void => {
|
||||
const target = <any> evt.target; // Convert to <any> to be able to use non-standard properties.
|
||||
if (target.result !== undefined || target.result !== null) {
|
||||
if (format == CoreFileProvider.FORMATJSON) {
|
||||
// Convert to object.
|
||||
const parsed = this.textUtils.parseJSON(target.result, null);
|
||||
|
||||
if (parsed == null) {
|
||||
reject('Error parsing JSON file.');
|
||||
}
|
||||
|
||||
resolve(parsed);
|
||||
} else {
|
||||
resolve(target.result);
|
||||
}
|
||||
} else if (target.error !== undefined || target.error !== null) {
|
||||
reject(target.error);
|
||||
} else {
|
||||
|
@ -728,19 +752,58 @@ export class CoreFileProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a dir.
|
||||
*
|
||||
* @param originalPath Path to the dir to move.
|
||||
* @param newPath New path of the dir.
|
||||
* @param destDirExists Set it to true if you know the directory where to put the dir exists. If false, the function will
|
||||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is moved.
|
||||
*/
|
||||
moveDir(originalPath: string, newPath: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.moveFileOrDir(originalPath, newPath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a file.
|
||||
*
|
||||
* @param originalPath Path to the file to move.
|
||||
* @param newPath New path of the file.
|
||||
* @param destDirExists Set it to true if you know the directory where to put the file exists. If false, the function will
|
||||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is moved.
|
||||
*/
|
||||
moveFile(originalPath: string, newPath: string): Promise<any> {
|
||||
moveFile(originalPath: string, newPath: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.moveFileOrDir(originalPath, newPath, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a file/dir.
|
||||
*
|
||||
* @param originalPath Path to the file/dir to move.
|
||||
* @param newPath New path of the file/dir.
|
||||
* @param isDir Whether it's a dir or a file.
|
||||
* @param destDirExists Set it to true if you know the directory where to put the file/dir exists. If false, the function will
|
||||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is moved.
|
||||
*/
|
||||
protected moveFileOrDir(originalPath: string, newPath: string, isDir?: boolean, destDirExists?: boolean): Promise<any> {
|
||||
const moveFn = isDir ? this.file.moveDir.bind(this.file) : this.file.moveFile.bind(this.file);
|
||||
|
||||
return this.init().then(() => {
|
||||
// Remove basePath if it's in the paths.
|
||||
originalPath = this.removeStartingSlash(originalPath.replace(this.basePath, ''));
|
||||
newPath = this.removeStartingSlash(newPath.replace(this.basePath, ''));
|
||||
|
||||
const newPathFileAndDir = this.getFileAndDirectoryFromPath(newPath);
|
||||
|
||||
if (newPathFileAndDir.directory && !destDirExists) {
|
||||
// Create the target directory if it doesn't exist.
|
||||
return this.createDir(newPathFileAndDir.directory);
|
||||
}
|
||||
}).then(() => {
|
||||
|
||||
if (this.isHTMLAPI) {
|
||||
// In Cordova API we need to calculate the longest matching path to make it work.
|
||||
// The function this.file.moveFile('a/', 'b/c.ext', 'a/', 'b/d.ext') doesn't work.
|
||||
|
@ -763,15 +826,15 @@ export class CoreFileProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return this.file.moveFile(commonPath, originalPath, commonPath, newPath);
|
||||
return moveFn(commonPath, originalPath, commonPath, newPath);
|
||||
} else {
|
||||
return this.file.moveFile(this.basePath, originalPath, this.basePath, newPath).catch((error) => {
|
||||
return moveFn(this.basePath, originalPath, this.basePath, newPath).catch((error) => {
|
||||
// The move can fail if the path has encoded characters. Try again if that's the case.
|
||||
const decodedOriginal = decodeURI(originalPath),
|
||||
decodedNew = decodeURI(newPath);
|
||||
|
||||
if (decodedOriginal != originalPath || decodedNew != newPath) {
|
||||
return this.file.moveFile(this.basePath, decodedOriginal, this.basePath, decodedNew);
|
||||
return moveFn(this.basePath, decodedOriginal, this.basePath, decodedNew);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
@ -780,16 +843,46 @@ export class CoreFileProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a directory.
|
||||
*
|
||||
* @param from Path to the directory to move.
|
||||
* @param to New path of the directory.
|
||||
* @param destDirExists Set it to true if you know the directory where to put the dir exists. If false, the function will
|
||||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
copyDir(from: string, to: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.copyFileOrDir(from, to, true, destDirExists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file.
|
||||
*
|
||||
* @param from Path to the file to move.
|
||||
* @param to New path of the file.
|
||||
* @param destDirExists Set it to true if you know the directory where to put the file exists. If false, the function will
|
||||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
copyFile(from: string, to: string): Promise<any> {
|
||||
copyFile(from: string, to: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.copyFileOrDir(from, to, false, destDirExists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file or a directory.
|
||||
*
|
||||
* @param from Path to the file/dir to move.
|
||||
* @param to New path of the file/dir.
|
||||
* @param isDir Whether it's a dir or a file.
|
||||
* @param destDirExists Set it to true if you know the directory where to put the file/dir exists. If false, the function will
|
||||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
protected copyFileOrDir(from: string, to: string, isDir?: boolean, destDirExists?: boolean): Promise<any> {
|
||||
let fromFileAndDir,
|
||||
toFileAndDir;
|
||||
const copyFn = isDir ? this.file.copyDir.bind(this.file) : this.file.copyFile.bind(this.file);
|
||||
|
||||
return this.init().then(() => {
|
||||
// Paths cannot start with "/". Remove basePath if present.
|
||||
|
@ -799,7 +892,7 @@ export class CoreFileProvider {
|
|||
fromFileAndDir = this.getFileAndDirectoryFromPath(from);
|
||||
toFileAndDir = this.getFileAndDirectoryFromPath(to);
|
||||
|
||||
if (toFileAndDir.directory) {
|
||||
if (toFileAndDir.directory && !destDirExists) {
|
||||
// Create the target directory if it doesn't exist.
|
||||
return this.createDir(toFileAndDir.directory);
|
||||
}
|
||||
|
@ -809,15 +902,15 @@ export class CoreFileProvider {
|
|||
const fromDir = this.textUtils.concatenatePaths(this.basePath, fromFileAndDir.directory),
|
||||
toDir = this.textUtils.concatenatePaths(this.basePath, toFileAndDir.directory);
|
||||
|
||||
return this.file.copyFile(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name);
|
||||
return copyFn(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name);
|
||||
} else {
|
||||
return this.file.copyFile(this.basePath, from, this.basePath, to).catch((error) => {
|
||||
return copyFn(this.basePath, from, this.basePath, to).catch((error) => {
|
||||
// The copy can fail if the path has encoded characters. Try again if that's the case.
|
||||
const decodedFrom = decodeURI(from),
|
||||
decodedTo = decodeURI(to);
|
||||
|
||||
if (from != decodedFrom || to != decodedTo) {
|
||||
return this.file.copyFile(this.basePath, decodedFrom, this.basePath, decodedTo);
|
||||
return copyFn(this.basePath, decodedFrom, this.basePath, decodedTo);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue