diff --git a/src/addons/mod/assign/submission/file/services/handler.ts b/src/addons/mod/assign/submission/file/services/handler.ts index ae771cf8d..e55694ce6 100644 --- a/src/addons/mod/assign/submission/file/services/handler.ts +++ b/src/addons/mod/assign/submission/file/services/handler.ts @@ -25,7 +25,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader'; import { CoreFileEntry, CoreFileHelper } from '@services/file-helper'; import { CoreFileSession } from '@services/file-session'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreWSFile } from '@services/ws'; import { makeSingleton } from '@singletons'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; @@ -242,7 +242,7 @@ export class AddonModAssignSubmissionFileHandlerService implements AddonModAssig // Data has changed, we need to upload new files and re-upload all the existing files. const currentFiles = CoreFileSession.getFiles(ADDON_MOD_ASSIGN_COMPONENT, assign.id); - const error = CoreUtils.hasRepeatedFilenames(currentFiles); + const error = CoreFileUtils.hasRepeatedFilenames(currentFiles); if (error) { throw error; diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts index d2d54aac7..04a356ec5 100644 --- a/src/addons/mod/forum/components/post/post.ts +++ b/src/addons/mod/forum/components/post/post.ts @@ -45,7 +45,7 @@ import { CoreSync } from '@services/sync'; import { CoreText } from '@singletons/text'; import { AddonModForumHelper } from '../../services/forum-helper'; import { AddonModForumOffline } from '../../services/forum-offline'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreRatingInfo } from '@features/rating/services/rating'; import { CoreForms } from '@singletons/form'; import { CoreFileEntry, CoreFileHelper } from '@services/file-helper'; @@ -506,7 +506,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges } // Use prepare post for edition to avoid re-uploading all files. - let filesToKeep = files.filter((file): file is CoreWSFile => !CoreUtils.isFileEntry(file)); + let filesToKeep = files.filter((file): file is CoreWSFile => !CoreFileUtils.isFileEntry(file)); let removedFiles: { filepath: string; filename: string }[] | undefined; if (previousAttachments.length && !filesToKeep.length) { diff --git a/src/core/features/fileuploader/services/fileuploader.ts b/src/core/features/fileuploader/services/fileuploader.ts index 6e6974ea1..4e331f001 100644 --- a/src/core/features/fileuploader/services/fileuploader.ts +++ b/src/core/features/fileuploader/services/fileuploader.ts @@ -19,6 +19,7 @@ import { MediaFile, CaptureError, CaptureVideoOptions } from '@awesome-cordova-p import { Subject } from 'rxjs'; import { CoreFile, CoreFileProvider } from '@services/file'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreFilepool } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; @@ -557,7 +558,7 @@ export class CoreFileUploaderProvider { await CoreFile.removeUnusedFiles(folderPath, files); await Promise.all(files.map(async (file) => { - if (!CoreUtils.isFileEntry(file)) { + if (!CoreFileUtils.isFileEntry(file)) { // It's an online file, add it to the result and ignore it. result.online.push({ filename: file.filename, @@ -632,7 +633,7 @@ export class CoreFileUploaderProvider { const usedNames: {[name: string]: CoreFileEntry} = {}; const filesToUpload: FileEntry[] = []; files.forEach((file) => { - if (CoreUtils.isFileEntry(file)) { + if (CoreFileUtils.isFileEntry(file)) { filesToUpload.push( file); } else { // It's an online file. @@ -679,9 +680,9 @@ export class CoreFileUploaderProvider { let fileName = ''; let fileEntry: FileEntry | undefined; - const isOnline = !CoreUtils.isFileEntry(file); + const isOnline = !CoreFileUtils.isFileEntry(file); - if (CoreUtils.isFileEntry(file)) { + if (CoreFileUtils.isFileEntry(file)) { // Local file, we already have the file entry. fileName = file.name; fileEntry = file; diff --git a/src/core/services/file-helper.ts b/src/core/services/file-helper.ts index 29b2aa706..7644b6196 100644 --- a/src/core/services/file-helper.ts +++ b/src/core/services/file-helper.ts @@ -17,6 +17,7 @@ import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { CoreNetwork } from '@services/network'; import { CoreFile } from '@services/file'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreFilepool } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreWS, CoreWSFile } from '@services/ws'; @@ -489,7 +490,7 @@ export class CoreFileHelperProvider { * @returns The file name. */ getFilenameFromPath(file: CoreFileEntry): string | undefined { - const path = CoreUtils.isFileEntry(file) ? file.fullPath : file.filepath; + const path = CoreFileUtils.isFileEntry(file) ? file.fullPath : file.filepath; if (path === undefined || path.length == 0) { return; diff --git a/src/core/services/file.ts b/src/core/services/file.ts index 9cc8bc66a..3f6fe80c3 100644 --- a/src/core/services/file.ts +++ b/src/core/services/file.ts @@ -17,7 +17,7 @@ import { Injectable } from '@angular/core'; import { FileEntry, DirectoryEntry, Entry, Metadata, IFile } from '@awesome-cordova-plugins/file/ngx'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreConstants } from '@/core/constants'; import { CoreError } from '@classes/errors/error'; @@ -1304,7 +1304,7 @@ export class CoreFileProvider { * @returns The file name. */ getFileName(file: CoreFileEntry): string | undefined { - return CoreUtils.isFileEntry(file) ? file.name : file.filename; + return CoreFileUtils.isFileEntry(file) ? file.name : file.filename; } } diff --git a/src/core/services/utils/mimetype.ts b/src/core/services/utils/mimetype.ts index 3125c6bf9..9b54f8d69 100644 --- a/src/core/services/utils/mimetype.ts +++ b/src/core/services/utils/mimetype.ts @@ -16,11 +16,11 @@ import { Injectable } from '@angular/core'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { CoreFile } from '@services/file'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreText } from '@singletons/text'; import { makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreWSFile } from '@services/ws'; -import { CoreUtils } from '@services/utils/utils'; import extToMime from '@/assets/exttomime.json'; import mimeToExt from '@/assets/mimetoext.json'; @@ -168,11 +168,11 @@ export class CoreMimetypeUtilsProvider { * @returns The embedded HTML string. */ getEmbeddedHtml(file: CoreFileEntry, path?: string): string { - const filename = CoreUtils.isFileEntry(file) ? (file as FileEntry).name : file.filename; - const extension = !CoreUtils.isFileEntry(file) && file.mimetype + const filename = CoreFileUtils.isFileEntry(file) ? (file as FileEntry).name : file.filename; + const extension = !CoreFileUtils.isFileEntry(file) && file.mimetype ? this.getExtension(file.mimetype) : (filename && this.getFileExtension(filename)); - const mimeType = !CoreUtils.isFileEntry(file) && file.mimetype + const mimeType = !CoreFileUtils.isFileEntry(file) && file.mimetype ? file.mimetype : (extension && this.getMimeType(extension)); @@ -185,7 +185,7 @@ export class CoreMimetypeUtilsProvider { // @todo linting: See if this can be removed (file as { embedType?: string }).embedType = embedType; - path = path ?? (CoreUtils.isFileEntry(file) ? CoreFile.getFileEntryURL(file) : CoreFileHelper.getFileUrl(file)); + path = path ?? (CoreFileUtils.isFileEntry(file) ? CoreFile.getFileEntryURL(file) : CoreFileHelper.getFileUrl(file)); path = path && CoreFile.convertFileSrc(path); switch (embedType) { @@ -424,7 +424,7 @@ export class CoreMimetypeUtilsProvider { let mimetype: string | undefined = ''; let extension: string | undefined = ''; - if (typeof obj == 'object' && CoreUtils.isFileEntry(obj)) { + if (typeof obj == 'object' && CoreFileUtils.isFileEntry(obj)) { // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable. filename = obj.name; } else if (typeof obj == 'object') { diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index aef33eabe..8de7d80bf 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { InAppBrowserObject } from '@awesome-cordova-plugins/in-app-browser'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { CoreFile } from '@services/file'; +import { CoreFileUtils } from '@singletons/file-utils'; import { CoreLang, CoreLangFormat } from '@services/lang'; import { CoreWS } from '@services/ws'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; @@ -288,7 +289,7 @@ export class CoreUtilsProvider { return source; } - if (this.valueIsFileEntry(source)) { + if (CoreFileUtils.valueIsFileEntry(source)) { // Don't clone FileEntry. It has a lot of depth and they shouldn't be modified. return source; } else if (Array.isArray(source)) { @@ -717,9 +718,10 @@ export class CoreUtilsProvider { * * @param file File. * @returns Type guard indicating if the file is a FileEntry. + * @deprecated since 5.0. Use CoreFile.isFileEntry singleton instead. */ isFileEntry(file: CoreFileEntry): file is FileEntry { - return 'isFile' in file; + return CoreFileUtils.isFileEntry(file); } /** @@ -727,11 +729,10 @@ export class CoreUtilsProvider { * * @param file Object to check. * @returns Type guard indicating if the file is a FileEntry. + * @deprecated since 5.0. Use CoreFile.valueIsFileEntry singleton instead. */ valueIsFileEntry(file: unknown): file is FileEntry { - // We cannot use instanceof because FileEntry is a type. Check some of the properties. - return !!(file && typeof file == 'object' && 'isFile' in file && 'filesystem' in file && - 'toInternalURL' in file && 'copyTo' in file); + return CoreFileUtils.valueIsFileEntry(file); } /** @@ -749,27 +750,10 @@ export class CoreUtilsProvider { * * @param files List of files. * @returns String with error message if repeated, false if no repeated. + * @deprecated since 5.0. Use CoreFileUtils.hasRepeatedFilenames instead. */ hasRepeatedFilenames(files: CoreFileEntry[]): string | false { - if (!files || !files.length) { - return false; - } - - const names: string[] = []; - - // Check if there are 2 files with the same name. - for (let i = 0; i < files.length; i++) { - const file = files[i]; - const name = (this.isFileEntry(file) ? file.name : file.filename) || ''; - - if (names.indexOf(name) > -1) { - return Translate.instant('core.filenameexist', { $a: name }); - } - - names.push(name); - } - - return false; + return CoreFileUtils.hasRepeatedFilenames(files); } /** @@ -956,7 +940,7 @@ export class CoreUtilsProvider { // Path needs to be decoded, the file won't be opened if the path has %20 instead of spaces and so. try { path = decodeURIComponent(path); - } catch (ex) { + } catch { // Error, use the original path. } diff --git a/src/core/singletons/file-utils.ts b/src/core/singletons/file-utils.ts new file mode 100644 index 000000000..8791544b4 --- /dev/null +++ b/src/core/singletons/file-utils.ts @@ -0,0 +1,79 @@ +// (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 { CoreFileEntry } from '@services/file-helper'; +import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; +import { Translate } from '@singletons'; + +/** + * Helpers to interact with the file system. + */ +export class CoreFileUtils { + + // Avoid creating singleton instances. + private constructor() { + // Nothing to do. + } + + /** + * Check if a file is a FileEntry + * + * @param file File. + * @returns Type guard indicating if the file is a FileEntry. + */ + static isFileEntry(file: CoreFileEntry): file is FileEntry { + return 'isFile' in file; + } + + /** + * Check if an unknown value is a FileEntry. + * + * @param file Object to check. + * @returns Type guard indicating if the file is a FileEntry. + */ + static valueIsFileEntry(file: unknown): file is FileEntry { + // We cannot use instanceof because FileEntry is a type. Check some of the properties. + return !!(file && typeof file == 'object' && 'isFile' in file && 'filesystem' in file && + 'toInternalURL' in file && 'copyTo' in file); + } + + /** + * Given a list of files, check if there are repeated names. + * + * @param files List of files. + * @returns String with error message if repeated, false if no repeated. + */ + static hasRepeatedFilenames(files: CoreFileEntry[]): string | false { + if (!files || !files.length) { + return false; + } + + const names: string[] = []; + + // Check if there are 2 files with the same name. + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const name = (this.isFileEntry(file) ? file.name : file.filename) || ''; + + if (names.indexOf(name) > -1) { + return Translate.instant('core.filenameexist', { $a: name }); + } + + names.push(name); + } + + return false; + } + +} diff --git a/src/core/singletons/window.ts b/src/core/singletons/window.ts index c14b8de93..6058d3723 100644 --- a/src/core/singletons/window.ts +++ b/src/core/singletons/window.ts @@ -83,7 +83,8 @@ export class CoreWindow { try { await CoreFileHelper.showConfirmOpenUnsupportedFile(false, { filename }); } catch { - return; // Cancelled, stop. + // Cancelled, stop. + return; } }