MOBILE-4081 h5p: Fix H5P assets broken on iOS update
parent
bf98c699da
commit
51ba1186d5
|
@ -18,6 +18,7 @@ import { CoreSites } from '@services/sites';
|
|||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import {
|
||||
CoreH5PCore,
|
||||
CoreH5PDependencyAsset,
|
||||
|
@ -64,7 +65,7 @@ export class CoreH5PFileStorage {
|
|||
const path = CoreText.concatenatePaths(cachedAssetsPath, fileName);
|
||||
|
||||
// Store concatenated content.
|
||||
const content = await this.concatenateFiles(assets, type);
|
||||
const content = await this.concatenateFiles(assets, type, cachedAssetsPath);
|
||||
|
||||
await CoreFile.writeFile(path, content);
|
||||
|
||||
|
@ -82,11 +83,11 @@ export class CoreH5PFileStorage {
|
|||
* Adds all files of a type into one file.
|
||||
*
|
||||
* @param assets A list of files.
|
||||
* @param type The type of files in assets. Either 'scripts' or 'styles'
|
||||
* @param type The type of files in assets. Either 'scripts' or 'styles'.
|
||||
* @param newFolder The new folder where the concatenated content will be stored.
|
||||
* @return Promise resolved with all of the files content in one string.
|
||||
*/
|
||||
protected async concatenateFiles(assets: CoreH5PDependencyAsset[], type: string): Promise<string> {
|
||||
const basePath = CoreFile.convertFileSrc(CoreFile.getBasePathInstant());
|
||||
protected async concatenateFiles(assets: CoreH5PDependencyAsset[], type: string, newFolder: string): Promise<string> {
|
||||
let content = '';
|
||||
|
||||
for (const i in assets) {
|
||||
|
@ -104,46 +105,22 @@ export class CoreH5PFileStorage {
|
|||
// Rewrite relative URLs used inside stylesheets.
|
||||
const matches = fileContent.match(/url\(['"]?([^"')]+)['"]?\)/ig);
|
||||
const assetPath = asset.path.replace(/(^\/|\/$)/g, ''); // Path without start/end slashes.
|
||||
const treated = {};
|
||||
const treated: Record<string, string> = {};
|
||||
|
||||
if (matches && matches.length) {
|
||||
matches.forEach((match) => {
|
||||
let url = match.replace(/(url\(['"]?|['"]?\)$)/ig, '');
|
||||
const url = match.replace(/(url\(['"]?|['"]?\)$)/ig, '');
|
||||
|
||||
if (treated[url] || url.match(/^(data:|([a-z0-9]+:)?\/)/i)) {
|
||||
return; // Not relative or already treated, skip.
|
||||
}
|
||||
|
||||
const pathSplit = assetPath.split('/');
|
||||
treated[url] = url;
|
||||
|
||||
/* Find "../" in the URL. If it exists, we have to remove "../" and switch the last folder in the
|
||||
filepath for the first folder in the url. */
|
||||
if (url.match(/^\.\.\//)) {
|
||||
// Split and remove empty values.
|
||||
const urlSplit = url.split('/').filter((i) => i);
|
||||
|
||||
// Remove the file name from the asset path.
|
||||
pathSplit.pop();
|
||||
|
||||
// Remove the first element from the file URL: ../ .
|
||||
urlSplit.shift();
|
||||
|
||||
// Put the url's first folder into the asset path.
|
||||
pathSplit[pathSplit.length - 1] = urlSplit[0];
|
||||
urlSplit.shift();
|
||||
|
||||
// Create the new URL and replace it in the file contents.
|
||||
url = pathSplit.join('/') + '/' + urlSplit.join('/');
|
||||
|
||||
} else {
|
||||
pathSplit[pathSplit.length - 1] = url; // Put the whole path to the end of the asset path.
|
||||
url = pathSplit.join('/');
|
||||
}
|
||||
const assetPathFolder = CoreFile.getFileAndDirectoryFromPath(assetPath).directory;
|
||||
|
||||
fileContent = fileContent.replace(
|
||||
new RegExp(CoreTextUtils.escapeForRegex(match), 'g'),
|
||||
'url("' + CoreText.concatenatePaths(basePath, url) + '")',
|
||||
'url("' + CorePath.changeRelativePath(assetPathFolder, url, newFolder) + '")',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ import { CoreUtils } from './utils/utils';
|
|||
import { CoreApp } from './app';
|
||||
import { CoreZoomLevel } from '@features/settings/services/settings-helper';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
|
||||
const VERSION_APPLIED = 'version_applied';
|
||||
import { CoreFile } from './file';
|
||||
import { CorePlatform } from './platform';
|
||||
|
||||
/**
|
||||
* Factory to handle app updates. This factory shouldn't be used outside of core.
|
||||
|
@ -36,6 +36,9 @@ const VERSION_APPLIED = 'version_applied';
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreUpdateManagerProvider {
|
||||
|
||||
protected static readonly VERSION_APPLIED = 'version_applied';
|
||||
protected static readonly PREVIOUS_APP_FOLDER = 'previous_app_folder';
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected doneDeferred: CorePromisedValue<void>;
|
||||
|
||||
|
@ -63,13 +66,22 @@ export class CoreUpdateManagerProvider {
|
|||
const promises: Promise<unknown>[] = [];
|
||||
const versionCode = CoreConstants.CONFIG.versioncode;
|
||||
|
||||
const versionApplied = await CoreConfig.get<number>(VERSION_APPLIED, 0);
|
||||
const [versionApplied, previousAppFolder, currentAppFolder] = await Promise.all([
|
||||
CoreConfig.get<number>(CoreUpdateManagerProvider.VERSION_APPLIED, 0),
|
||||
CoreConfig.get<string>(CoreUpdateManagerProvider.PREVIOUS_APP_FOLDER, ''),
|
||||
CorePlatform.isMobile() ? CoreUtils.ignoreErrors(CoreFile.getBasePath(), '') : '',
|
||||
]);
|
||||
|
||||
if (versionCode > versionApplied) {
|
||||
promises.push(this.checkCurrentSiteAllowed());
|
||||
}
|
||||
|
||||
if (versionCode >= 3950 && versionApplied < 3950 && versionApplied > 0) {
|
||||
if (
|
||||
(versionCode >= 3950 && versionApplied < 3950 && versionApplied > 0) ||
|
||||
(currentAppFolder && currentAppFolder !== previousAppFolder)
|
||||
) {
|
||||
// Delete content indexes if the app folder has changed.
|
||||
// This happens in iOS every time the app is updated, even if the version hasn't changed.
|
||||
promises.push(CoreH5P.h5pPlayer.deleteAllContentIndexes());
|
||||
}
|
||||
|
||||
|
@ -80,7 +92,10 @@ export class CoreUpdateManagerProvider {
|
|||
try {
|
||||
await Promise.all(promises);
|
||||
|
||||
await CoreConfig.set(VERSION_APPLIED, versionCode);
|
||||
await Promise.all([
|
||||
CoreConfig.set(CoreUpdateManagerProvider.VERSION_APPLIED, versionCode),
|
||||
currentAppFolder ? CoreConfig.set(CoreUpdateManagerProvider.PREVIOUS_APP_FOLDER, currentAppFolder) : undefined,
|
||||
]);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error);
|
||||
} finally {
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
// (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 { CoreText } from './text';
|
||||
|
||||
/**
|
||||
* Singleton with helper functions for paths.
|
||||
*/
|
||||
export class CorePath {
|
||||
|
||||
// Avoid creating singleton instances.
|
||||
private constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a relative path from a folder to another folder.
|
||||
*
|
||||
* E.g. if initial folder is foo/bar, and final folder is foo/baz/xyz, it will return ../bar/xyz.
|
||||
*
|
||||
* @param initialFolder The initial folder path.
|
||||
* @param finalFolder The final folder. The "root" should be the same as initialFolder.
|
||||
* @return Relative path.
|
||||
*/
|
||||
static calculateRelativePath(initialFolder: string, finalFolder: string): string {
|
||||
initialFolder = CoreText.removeStartingSlash(CoreText.removeEndingSlash(initialFolder));
|
||||
finalFolder = CoreText.removeStartingSlash(CoreText.removeEndingSlash(finalFolder));
|
||||
|
||||
if (initialFolder === finalFolder) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const initialFolderSplit = initialFolder === '' ? [] : initialFolder.split('/');
|
||||
const finalFolderSplit = finalFolder === '' ? [] : finalFolder.split('/');
|
||||
|
||||
let firstDiffIndex = initialFolderSplit.length > 0 && finalFolderSplit.length > 0 ?
|
||||
initialFolderSplit.findIndex((value, index) => value !== finalFolderSplit[index]) :
|
||||
0;
|
||||
|
||||
if (firstDiffIndex === -1) {
|
||||
// All elements in initial folder are equal. The first diff is the first element in the final folder.
|
||||
firstDiffIndex = initialFolderSplit.length;
|
||||
}
|
||||
|
||||
const newPathToFinalFolder = finalFolderSplit.slice(firstDiffIndex).join('/');
|
||||
|
||||
return '../'.repeat(initialFolderSplit.length - firstDiffIndex) + newPathToFinalFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a relative path (based on a certain folder) to a relative path based on a different folder.
|
||||
*
|
||||
* E.g. if current folder is foo/bar, relative URL is test.jpg and new folder is foo/baz,
|
||||
* it will return ../bar/test.jpg.
|
||||
*
|
||||
* @param currentFolder The current folder path.
|
||||
* @param path The relative path.
|
||||
* @param newFolder The folder to use to calculate the new relative path. The "root" should be the same as currentFolder.
|
||||
* @return Relative path.
|
||||
*/
|
||||
static changeRelativePath(currentFolder: string, path: string, newFolder: string): string {
|
||||
return CoreText.concatenatePaths(CorePath.calculateRelativePath(newFolder, currentFolder), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate two paths, adding a slash between them if needed.
|
||||
*
|
||||
* @param leftPath Left path.
|
||||
* @param rightPath Right path.
|
||||
* @return Concatenated path.
|
||||
*/
|
||||
static concatenatePaths(leftPath: string, rightPath: string): string {
|
||||
if (!leftPath) {
|
||||
return rightPath;
|
||||
} else if (!rightPath) {
|
||||
return leftPath;
|
||||
}
|
||||
|
||||
const lastCharLeft = leftPath.slice(-1);
|
||||
const firstCharRight = rightPath.charAt(0);
|
||||
|
||||
if (lastCharLeft === '/' && firstCharRight === '/') {
|
||||
return leftPath + rightPath.substring(1);
|
||||
} else if (lastCharLeft !== '/' && firstCharRight !== '/') {
|
||||
return leftPath + '/' + rightPath;
|
||||
} else {
|
||||
return leftPath + rightPath;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// (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 { CorePath } from '@singletons/path';
|
||||
|
||||
describe('CorePath', () => {
|
||||
|
||||
it('calculates relative paths from one folder to another', () => {
|
||||
expect(CorePath.calculateRelativePath('foo/bar', 'foo/bar')).toEqual('');
|
||||
expect(CorePath.calculateRelativePath('/foo/bar', 'foo/bar/')).toEqual('');
|
||||
expect(CorePath.calculateRelativePath('foo/bar', 'foo/baz')).toEqual('../baz');
|
||||
expect(CorePath.calculateRelativePath('foo', 'baz')).toEqual('../baz');
|
||||
expect(CorePath.calculateRelativePath('foo/bar/baz', 'foo/baz')).toEqual('../../baz');
|
||||
expect(CorePath.calculateRelativePath('foo/baz', 'foo/bar/baz')).toEqual('../bar/baz');
|
||||
expect(CorePath.calculateRelativePath('foo/bar/baz', 'foo/bar')).toEqual('../');
|
||||
expect(CorePath.calculateRelativePath('foo/bar', 'foo/bar/baz')).toEqual('baz');
|
||||
expect(CorePath.calculateRelativePath('', 'foo')).toEqual('foo');
|
||||
expect(CorePath.calculateRelativePath('foo', '')).toEqual('../');
|
||||
});
|
||||
|
||||
it('changes relative paths to a different folder', () => {
|
||||
expect(CorePath.changeRelativePath('foo/bar', 'test.png', 'foo/bar')).toEqual('test.png');
|
||||
expect(CorePath.changeRelativePath('/foo/bar', 'test.png', 'foo/bar/')).toEqual('test.png');
|
||||
expect(CorePath.changeRelativePath('foo/bar', 'test.png', 'foo/baz')).toEqual('../bar/test.png');
|
||||
expect(CorePath.changeRelativePath('foo/bar', '../xyz/test.png', 'foo/baz')).toEqual('../bar/../xyz/test.png');
|
||||
expect(CorePath.changeRelativePath('foo', 'bar/test.png', 'baz')).toEqual('../foo/bar/test.png');
|
||||
expect(CorePath.changeRelativePath('foo/bar/baz', 'test.png', 'foo/baz')).toEqual('../bar/baz/test.png');
|
||||
expect(CorePath.changeRelativePath('foo/bar/baz', 'test.png', 'foo/bar')).toEqual('baz/test.png');
|
||||
expect(CorePath.changeRelativePath('foo/bar/baz', 'test.png', 'foo/bar/xyz')).toEqual('../baz/test.png');
|
||||
expect(CorePath.changeRelativePath('', 'test.png', 'foo')).toEqual('../test.png');
|
||||
expect(CorePath.changeRelativePath('foo', 'test.png', '')).toEqual('foo/test.png');
|
||||
});
|
||||
|
||||
it('concatenates paths', () => {
|
||||
expect(CorePath.concatenatePaths('', 'foo/bar')).toEqual('foo/bar');
|
||||
expect(CorePath.concatenatePaths('foo/bar', '')).toEqual('foo/bar');
|
||||
expect(CorePath.concatenatePaths('foo', 'bar')).toEqual('foo/bar');
|
||||
expect(CorePath.concatenatePaths('foo/', 'bar')).toEqual('foo/bar');
|
||||
expect(CorePath.concatenatePaths('foo', '/bar')).toEqual('foo/bar');
|
||||
expect(CorePath.concatenatePaths('foo/', '/bar')).toEqual('foo/bar');
|
||||
expect(CorePath.concatenatePaths('foo/bar', 'baz')).toEqual('foo/bar/baz');
|
||||
});
|
||||
|
||||
});
|
|
@ -36,14 +36,4 @@ describe('CoreText singleton', () => {
|
|||
expect(CoreText.removeStartingSlash('//foo')).toEqual('/foo');
|
||||
});
|
||||
|
||||
it('concatenates paths', () => {
|
||||
expect(CoreText.concatenatePaths('', 'foo/bar')).toEqual('foo/bar');
|
||||
expect(CoreText.concatenatePaths('foo/bar', '')).toEqual('foo/bar');
|
||||
expect(CoreText.concatenatePaths('foo', 'bar')).toEqual('foo/bar');
|
||||
expect(CoreText.concatenatePaths('foo/', 'bar')).toEqual('foo/bar');
|
||||
expect(CoreText.concatenatePaths('foo', '/bar')).toEqual('foo/bar');
|
||||
expect(CoreText.concatenatePaths('foo/', '/bar')).toEqual('foo/bar');
|
||||
expect(CoreText.concatenatePaths('foo/bar', 'baz')).toEqual('foo/bar/baz');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CorePath } from './path';
|
||||
|
||||
/**
|
||||
* Singleton with helper functions for text manipulation.
|
||||
*/
|
||||
|
@ -74,24 +76,10 @@ export class CoreText {
|
|||
* @param leftPath Left path.
|
||||
* @param rightPath Right path.
|
||||
* @return Concatenated path.
|
||||
* @deprecated since 4.1.0. Please use CorePath.concatenatePaths instead.
|
||||
*/
|
||||
static concatenatePaths(leftPath: string, rightPath: string): string {
|
||||
if (!leftPath) {
|
||||
return rightPath;
|
||||
} else if (!rightPath) {
|
||||
return leftPath;
|
||||
}
|
||||
|
||||
const lastCharLeft = leftPath.slice(-1);
|
||||
const firstCharRight = rightPath.charAt(0);
|
||||
|
||||
if (lastCharLeft === '/' && firstCharRight === '/') {
|
||||
return leftPath + rightPath.substring(1);
|
||||
} else if (lastCharLeft !== '/' && firstCharRight !== '/') {
|
||||
return leftPath + '/' + rightPath;
|
||||
} else {
|
||||
return leftPath + rightPath;
|
||||
}
|
||||
return CorePath.concatenatePaths(leftPath, rightPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue