Merge pull request #2966 from dpalou/MOBILE-3773

Mobile 3773
main
Pau Ferrer Ocaña 2021-10-04 11:19:09 +02:00 committed by GitHub
commit d20ff60118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 119 additions and 22 deletions

View File

@ -266,9 +266,11 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
finalUrl = CoreFile.convertFileSrc(finalUrl);
}
if (!CoreUrlUtils.isLocalFileUrl(finalUrl)) {
if (!CoreUrlUtils.isLocalFileUrl(finalUrl) && !finalUrl.includes('#')) {
/* In iOS, if we use the same URL in embedded file and background download then the download only
downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
downloads a few bytes (cached ones). Add an anchor to the URL so both URLs are different.
Don't add this anchor if the URL already has an anchor, otherwise other anchors might not work.
The downloaded URL won't have anchors so the URLs will already be different. */
finalUrl = finalUrl + '#moodlemobile-embedded';
}

View File

@ -555,7 +555,7 @@ export class CoreFormatTextDirective implements OnChanges {
});
videos.forEach((video) => {
this.treatMedia(video);
this.treatMedia(video, true);
});
iframes.forEach((iframe) => {
@ -673,8 +673,9 @@ export class CoreFormatTextDirective implements OnChanges {
* Add media adapt class and apply CoreExternalContentDirective to the media element and its sources and tracks.
*
* @param element Video or audio to treat.
* @param isVideo Whether it's a video.
*/
protected treatMedia(element: HTMLElement): void {
protected treatMedia(element: HTMLElement, isVideo: boolean = false): void {
this.addMediaAdaptClass(element);
this.addExternalContent(element);
@ -692,8 +693,16 @@ export class CoreFormatTextDirective implements OnChanges {
const sources = Array.from(element.querySelectorAll('source'));
const tracks = Array.from(element.querySelectorAll('track'));
const hasPoster = isVideo && !!element.getAttribute('poster');
if (isVideo && !hasPoster) {
this.fixVideoSrcPlaceholder(element);
}
sources.forEach((source) => {
if (isVideo && !hasPoster) {
this.fixVideoSrcPlaceholder(source);
}
source.setAttribute('target-src', source.getAttribute('src') || '');
source.removeAttribute('src');
this.addExternalContent(source);
@ -709,6 +718,24 @@ export class CoreFormatTextDirective implements OnChanges {
});
}
/**
* Try to fix the placeholder displayed when a video doesn't have a poster.
*
* @param element Element to fix.
*/
protected fixVideoSrcPlaceholder(element: HTMLElement): void {
const src = element.getAttribute('src');
if (!src) {
return;
}
if (src.match(/#t=\d/)) {
return;
}
element.setAttribute('src', src + '#t=0.001');
}
/**
* Add media adapt class and treat the iframe source.
*

View File

@ -47,6 +47,7 @@ import {
CoreFilepoolQueueDBEntry,
} from '@services/database/filepool';
import { CoreFileHelper } from './file-helper';
import { CoreUrl } from '@singletons/url';
/*
* Factory for handling downloading files and retrieve downloaded files.
@ -540,8 +541,12 @@ export class CoreFilepoolProvider {
await site.getDb().deleteRecords(PACKAGES_TABLE_NAME);
entries.forEach((entry) => {
if (!entry.component) {
return;
}
// Trigger module status changed, setting it as not downloaded.
this.triggerPackageStatusChanged(siteId, CoreConstants.NOT_DOWNLOADED, entry.component!, entry.componentId);
this.triggerPackageStatusChanged(siteId, CoreConstants.NOT_DOWNLOADED, entry.component, entry.componentId);
});
}
@ -668,6 +673,13 @@ export class CoreFilepoolProvider {
poolFileObject?: CoreFilepoolFileEntry,
): Promise<string> {
const fileId = this.getFileIdByUrl(fileUrl);
// Extract the anchor from the URL (if any).
const anchor = CoreUrl.getUrlAnchor(fileUrl);
if (anchor) {
fileUrl = fileUrl.replace(anchor, '');
}
const extension = CoreMimetypeUtils.guessExtensionFromUrl(fileUrl);
const addExtension = typeof filePath == 'undefined';
const path = filePath || (await this.getFilePath(siteId, fileId, extension));
@ -680,7 +692,7 @@ export class CoreFilepoolProvider {
const downloadId = this.getFileDownloadId(fileUrl, path);
if (this.filePromises[siteId] && this.filePromises[siteId][downloadId]) {
if (this.filePromises[siteId] && this.filePromises[siteId][downloadId] !== undefined) {
// There's already a download ongoing for this file in this location, return the promise.
return this.filePromises[siteId][downloadId];
} else if (!this.filePromises[siteId]) {
@ -708,7 +720,8 @@ export class CoreFilepoolProvider {
extension: fileEntry.extension,
});
return fileEntry.toURL();
// Add the anchor again to the local URL.
return fileEntry.toURL() + (anchor || '');
}).finally(() => {
// Download finished, delete the promise.
delete this.filePromises[siteId][downloadId];
@ -753,11 +766,11 @@ export class CoreFilepoolProvider {
if (dirPath) {
// Calculate the path to the file.
path = file.filename;
path = file.filename || '';
if (file.filepath && file.filepath !== '/') {
path = file.filepath.substr(1) + path;
}
path = CoreTextUtils.concatenatePaths(dirPath, path!);
path = CoreTextUtils.concatenatePaths(dirPath, path);
}
if (prefetch) {
@ -806,7 +819,7 @@ export class CoreFilepoolProvider {
): Promise<void> {
const packageId = this.getPackageId(component, componentId);
if (this.packagesPromises[siteId] && this.packagesPromises[siteId][packageId]) {
if (this.packagesPromises[siteId] && this.packagesPromises[siteId][packageId] !== undefined) {
// There's already a download ongoing for this package, return the promise.
return this.packagesPromises[siteId][packageId];
} else if (!this.packagesPromises[siteId]) {
@ -847,11 +860,11 @@ export class CoreFilepoolProvider {
if (dirPath) {
// Calculate the path to the file.
path = file.filename;
path = file.filename || '';
if (file.filepath && file.filepath !== '/') {
path = file.filepath.substr(1) + path;
}
path = CoreTextUtils.concatenatePaths(dirPath, path!);
path = CoreTextUtils.concatenatePaths(dirPath, path);
}
if (prefetch) {
@ -1011,7 +1024,10 @@ export class CoreFilepoolProvider {
url = await this.getInternalUrlById(siteId, fileId);
}
return finishSuccessfulDownload(url);
// Add the anchor to the local URL if any.
const anchor = CoreUrl.getUrlAnchor(fileUrl);
return finishSuccessfulDownload(url + (anchor || ''));
} catch (error) {
// The file is not downloaded or it's outdated.
this.notifyFileDownloading(siteId, fileId, links);
@ -1280,6 +1296,9 @@ export class CoreFilepoolProvider {
});
}
// Remove the anchor.
url = CoreUrl.removeUrlAnchor(url);
// Try to guess the filename the target file should have.
// We want to keep the original file name so people can easily identify the files after the download.
const filename = this.guessFilenameFromUrl(url);
@ -1442,7 +1461,7 @@ export class CoreFilepoolProvider {
return CoreConstants.NOT_DOWNLOADABLE;
}
fileUrl = CoreFileHelper.getFileUrl(file);
fileUrl = CoreUrl.removeUrlAnchor(CoreFileHelper.getFileUrl(file));
timemodified = file.timemodified || timemodified;
revision = revision || this.getRevisionFromUrl(fileUrl);
const fileId = this.getFileIdByUrl(fileUrl);
@ -1459,7 +1478,7 @@ export class CoreFilepoolProvider {
const downloadId = this.getFileDownloadId(fileUrl, filePath);
if (this.filePromises[siteId] && this.filePromises[siteId][downloadId]) {
if (this.filePromises[siteId] && this.filePromises[siteId][downloadId] !== undefined) {
return CoreConstants.DOWNLOADING;
}
@ -1552,11 +1571,14 @@ export class CoreFilepoolProvider {
try {
// We found the file entry, now look for the file on disk.
if (mode === 'src') {
return await this.getInternalSrcById(siteId, fileId);
} else {
return await this.getInternalUrlById(siteId, fileId);
}
const path = mode === 'src' ?
await this.getInternalSrcById(siteId, fileId) :
await this.getInternalUrlById(siteId, fileId);
// Add the anchor to the local URL if any.
const anchor = CoreUrl.getUrlAnchor(fileUrl);
return path + (anchor || '');
} catch (error) {
// The file is not on disk.
// We could not retrieve the file, delete the entries associated with that ID.
@ -1730,7 +1752,7 @@ export class CoreFilepoolProvider {
*/
getPackageDownloadPromise(siteId: string, component: string, componentId?: string | number): Promise<void> | undefined {
const packageId = this.getPackageId(component, componentId);
if (this.packagesPromises[siteId] && this.packagesPromises[siteId][packageId]) {
if (this.packagesPromises[siteId] && this.packagesPromises[siteId][packageId] !== undefined) {
return this.packagesPromises[siteId][packageId];
}
}
@ -2745,7 +2767,7 @@ export class CoreFilepoolProvider {
await site.getDb().updateRecords(PACKAGES_TABLE_NAME, newData, { id: packageId });
// Success updating, trigger event.
this.triggerPackageStatusChanged(site.id!, newData.status, component, componentId);
this.triggerPackageStatusChanged(site.getId(), newData.status, component, componentId);
return newData.status;
}

View File

@ -313,6 +313,11 @@ export class CoreMimetypeUtilsProvider {
if (position > -1) {
candidate = candidate.substr(0, position);
}
// Remove anchor if any.
position = candidate.indexOf('#');
if (position > -1) {
candidate = candidate.substr(0, position);
}
if (EXTENSION_REGEX.test(candidate)) {
extension = candidate;

View File

@ -58,6 +58,10 @@ export class CoreUrlUtilsProvider {
* @return URL with params.
*/
addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
// Remove any existing anchor to add the params before it.
const urlAndAnchor = url.split('#');
url = urlAndAnchor[0];
let separator = url.indexOf('?') != -1 ? '&' : '?';
for (const key in params) {
@ -75,6 +79,15 @@ export class CoreUrlUtilsProvider {
}
}
// Re-add the anchor if any.
if (urlAndAnchor.length > 1) {
// Remove the URL from the array.
urlAndAnchor.shift();
// Use a join in case there is more than one #.
url += '#' + urlAndAnchor.join('#');
}
if (anchor) {
url += '#' + anchor;
}

View File

@ -203,4 +203,32 @@ export class CoreUrl {
&& CoreText.removeEndingSlash(partsA?.path) === CoreText.removeEndingSlash(partsB?.path);
}
/**
* Get the anchor of a URL. If there's more than one they'll all be returned, separated by #.
* E.g. myurl.com#foo=1#bar=2 will return #foo=1#bar=2.
*
* @param url URL.
* @return Anchor, undefined if no anchor.
*/
static getUrlAnchor(url: string): string | undefined {
const firstAnchorIndex = url.indexOf('#');
if (firstAnchorIndex === -1) {
return;
}
return url.substr(firstAnchorIndex);
}
/**
* Remove the anchor from a URL.
*
* @param url URL.
* @return URL without anchor if any.
*/
static removeUrlAnchor(url: string): string {
const urlAndAnchor = url.split('#');
return urlAndAnchor[0];
}
}