// (C) Copyright 2015 Martin Dougiamas // // 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'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from './app'; import { CoreFileProvider } from './file'; import { CoreFilepoolProvider } from './filepool'; import { CoreSitesProvider } from './sites'; import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; /** * Provider to provide some helper functions regarding files and packages. */ @Injectable() export class CoreFileHelperProvider { constructor(private fileProvider: CoreFileProvider, private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private translate: TranslateService, private utils: CoreUtilsProvider) { } /** * Convenience function to open a file, downloading it if needed. * * @param {any} file The file to download. * @param {string} [component] The component to link the file to. * @param {string|number} [componentId] An ID to use in conjunction with the component. * @param {string} [state] The file's state. If not provided, it will be calculated. * @param {Function} [onProgress] Function to call on progress. * @param {string} [siteId] The site ID. If not defined, current site. * @return {Promise} Resolved on success. */ downloadAndOpenFile(file: any, component: string, componentId: string | number, state?: string, onProgress?: (event: any) => any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const fileUrl = this.getFileUrl(file), timemodified = this.getFileTimemodified(file); return this.downloadFileIfNeeded(file, fileUrl, component, componentId, timemodified, state, onProgress, siteId) .then((url) => { if (!url) { return; } if (url.indexOf('http') === 0) { return this.utils.openOnlineFile(url).catch((error) => { // Error opening the file, some apps don't allow opening online files. if (!this.fileProvider.isAvailable()) { return Promise.reject(error); } // Get the state. if (state) { return state; } else { return this.filepoolProvider.getFileStateByUrl(siteId, fileUrl, timemodified); } }).then((state) => { if (state == CoreConstants.DOWNLOADING) { return Promise.reject(this.translate.instant('core.erroropenfiledownloading')); } let promise; if (state === CoreConstants.NOT_DOWNLOADED) { // File is not downloaded, download and then return the local URL. promise = this.downloadFile(fileUrl, component, componentId, timemodified, onProgress, file, siteId); } else { // File is outdated and can't be opened in online, return the local URL. promise = this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl); } return promise.then((url) => { return this.utils.openFile(url); }); }); } else { return this.utils.openFile(url); } }); } /** * Download a file if it needs to be downloaded. * * @param {any} file The file to download. * @param {string} fileUrl The file URL. * @param {string} [component] The component to link the file to. * @param {string|number} [componentId] An ID to use in conjunction with the component. * @param {number} [timemodified] The time this file was modified. * @param {string} [state] The file's state. If not provided, it will be calculated. * @param {Function} [onProgress] Function to call on progress. * @param {string} [siteId] The site ID. If not defined, current site. * @return {Promise} Resolved with the URL to use on success. */ protected downloadFileIfNeeded(file: any, fileUrl: string, component?: string, componentId?: string | number, timemodified?: number, state?: string, onProgress?: (event: any) => any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.sitesProvider.getSite(siteId).then((site) => { const fixedUrl = site.fixPluginfileURL(fileUrl); if (this.fileProvider.isAvailable()) { let promise; if (state) { promise = Promise.resolve(state); } else { // Calculate the state. promise = this.filepoolProvider.getFileStateByUrl(siteId, fileUrl, timemodified); } return promise.then((state) => { // The file system is available. const isWifi = !this.appProvider.isNetworkAccessLimited(), isOnline = this.appProvider.isOnline(); if (state == CoreConstants.DOWNLOADED) { // File is downloaded, get the local file URL. return this.filepoolProvider.getUrlByUrl( siteId, fileUrl, component, componentId, timemodified, false, false, file); } else { if (!isOnline && !this.isStateDownloaded(state)) { // Not downloaded and user is offline, reject. return Promise.reject(this.translate.instant('core.networkerrormsg')); } if (onProgress) { // This call can take a while. Send a fake event to notify that we're doing some calculations. onProgress({calculating: true}); } return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, file.filesize).then(() => { if (state == CoreConstants.DOWNLOADING) { // It's already downloading, stop. return; } // Download and then return the local URL. return this.downloadFile(fileUrl, component, componentId, timemodified, onProgress, file, siteId); }, () => { // Start the download if in wifi, but return the URL right away so the file is opened. if (isWifi && isOnline) { this.downloadFile(fileUrl, component, componentId, timemodified, onProgress, file, siteId); } if (!this.isStateDownloaded(state) || isOnline) { // Not downloaded or online, return the online URL. return fixedUrl; } else { // Outdated but offline, so we return the local URL. return this.filepoolProvider.getUrlByUrl( siteId, fileUrl, component, componentId, timemodified, false, false, file); } }); } }); } else { // Use the online URL. return fixedUrl; } }); } /** * Download the file. * * @param {string} fileUrl The file URL. * @param {string} [component] The component to link the file to. * @param {string|number} [componentId] An ID to use in conjunction with the component. * @param {number} [timemodified] The time this file was modified. * @param {Function} [onProgress] Function to call on progress. * @param {any} [file] The file to download. * @param {string} [siteId] The site ID. If not defined, current site. * @return {Promise} Resolved with internal URL on success, rejected otherwise. */ downloadFile(fileUrl: string, component?: string, componentId?: string | number, timemodified?: number, onProgress?: (event: any) => any, file?: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Get the site and check if it can download files. return this.sitesProvider.getSite(siteId).then((site) => { if (!site.canDownloadFiles()) { return Promise.reject(this.translate.instant('core.cannotdownloadfiles')); } return this.filepoolProvider.downloadUrl(siteId, fileUrl, false, component, componentId, timemodified, onProgress, undefined, file).catch((error) => { // Download failed, check the state again to see if the file was downloaded before. return this.filepoolProvider.getFileStateByUrl(siteId, fileUrl, timemodified).then((state) => { if (this.isStateDownloaded(state)) { return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl); } else { return Promise.reject(error); } }); }); }); } /** * Get the file's URL. * * @param {any} file The file. */ getFileUrl(file: any): string { return file.fileurl || file.url; } /** * Get the file's timemodified. * * @param {any} file The file. */ getFileTimemodified(file: any): number { return file.timemodified || 0; } /** * Check if a state is downloaded or outdated. * * @param {string} state The state to check. */ isStateDownloaded(state: string): boolean { return state === CoreConstants.DOWNLOADED || state === CoreConstants.OUTDATED; } /** * Whether the file has to be opened in browser (external repository). * The file must have a mimetype attribute. * * @param {any} file The file to check. * @return {boolean} Whether the file should be opened in browser. */ shouldOpenInBrowser(file: any): boolean { if (!file || !file.isexternalfile || !file.mimetype) { return false; } const mimetype = file.mimetype; if (mimetype.indexOf('application/vnd.google-apps.') != -1) { // Google Docs file, always open in browser. return true; } if (file.repositorytype == 'onedrive') { // In OneDrive, open in browser the office docs return mimetype.indexOf('application/vnd.openxmlformats-officedocument') != -1 || mimetype == 'text/plain' || mimetype == 'document/unknown'; } return false; } }