// (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 { CoreAppProvider } from '../../../providers/app'; import { CoreEventsProvider } from '../../../providers/events'; import { CoreFileProvider } from '../../../providers/file'; import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreSitesProvider } from '../../../providers/sites'; import { CoreMimetypeUtilsProvider } from '../../../providers/utils/mimetype'; import { CoreTextUtilsProvider } from '../../../providers/utils/text'; import { Md5 } from 'ts-md5/dist/md5'; import { SQLiteDB } from '../../../classes/sqlitedb'; /** * Service to share files with the app. */ @Injectable() export class CoreSharedFilesProvider { public static SHARED_FILES_FOLDER = 'sharedfiles'; // Variables for the database. protected SHARED_FILES_TABLE = 'wscache'; protected tableSchema = { name: this.SHARED_FILES_TABLE, columns: [ { name: 'id', type: 'TEXT', primaryKey: true } ] } protected logger; protected appDB: SQLiteDB; constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider) { this.logger = logger.getInstance('CoreSharedFilesProvider'); this.appDB = appProvider.getDB(); this.appDB.createTableFromSchema(this.tableSchema); } /** * Checks if there is a new file received in iOS. If more than one file is found, treat only the first one. * The file returned is marked as "treated" and will be deleted in the next execution. * * @return {Promise} Promise resolved with a new file to be treated. If no new files found, promise is rejected. */ checkIOSNewFiles() : Promise { this.logger.debug('Search for new files on iOS'); return this.fileProvider.getDirectoryContents('Inbox').then((entries) => { if (entries.length > 0) { let promises = [], fileToReturn; entries.forEach((entry) => { const fileId = this.getFileId(entry); // Check if file was already treated. promises.push(this.isFileTreated(fileId).then(() => { // File already treated, delete it. Don't return delete promise, we'll ignore errors. this.deleteInboxFile(entry); }).catch(() => { // File not treated before. this.logger.debug('Found new file ' + entry.name + ' shared with the app.'); if (!fileToReturn) { fileToReturn = entry; } })); }); return Promise.all(promises).then(() => { let fileId; if (fileToReturn) { // Mark it as "treated". fileId = this.getFileId(fileToReturn); return this.markAsTreated(fileId).then(() => { this.logger.debug('File marked as "treated": ' + fileToReturn.name); return fileToReturn; }); } else { return Promise.reject(null); } }); } else { return Promise.reject(null); } }); } /** * Deletes a file in the Inbox folder (shared with the app). * * @param {any} entry FileEntry. * @return {Promise} Promise resolved when done, rejected otherwise. */ deleteInboxFile(entry: any) : Promise { this.logger.debug('Delete inbox file: ' + entry.name); return this.fileProvider.removeFileByFileEntry(entry).catch(() => { // Ignore errors. }).then(() => { return this.unmarkAsTreated(this.getFileId(entry)).then(() => { this.logger.debug('"Treated" mark removed from file: ' + entry.name); }).catch((error) => { this.logger.debug('Error deleting "treated" mark from file: ' + entry.name, error); return Promise.reject(error); }); }); } /** * Get the ID of a file for managing "treated" files. * * @param {any} entry FileEntry. * @return {string} File ID. */ protected getFileId(entry: any) : string { return Md5.hashAsciiStr(entry.name); } /** * Get the shared files stored in a site. * * @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [path] Path to search inside the site shared folder. * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @return {Promise} Promise resolved with the files. */ getSiteSharedFiles(siteId?: string, path?: string, mimetypes?: string[]) : Promise { let pathToGet = this.getSiteSharedFilesDirPath(siteId); if (path) { pathToGet = this.textUtils.concatenatePaths(pathToGet, path); } return this.fileProvider.getDirectoryContents(pathToGet).then((files) => { if (mimetypes) { // Only show files with the right mimetype and the ones we cannot determine the mimetype. files = files.filter((file) => { const extension = this.mimeUtils.getFileExtension(file.name), mimetype = this.mimeUtils.getMimeType(extension); return !mimetype || mimetypes.indexOf(mimetype) > -1; }); } return files; }).catch(() => { // Directory not found, return empty list. return []; }); } /** * Get the path to a site's shared files folder. * * @param {string} [siteId] Site ID. If not defined, current site. * @return {string} Path. */ getSiteSharedFilesDirPath(siteId?: string) : string { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.fileProvider.getSiteFolder(siteId) + '/' + CoreSharedFilesProvider.SHARED_FILES_FOLDER; } /** * Check if a file has been treated already. * * @param {string} fileId File ID. * @return {Promise} Resolved if treated, rejected otherwise. */ protected isFileTreated(fileId: string) : Promise { return this.appDB.getRecord(this.SHARED_FILES_TABLE, {id: fileId}); } /** * Mark a file as treated. * * @param {string} fileId File ID. * @return {Promise} Promise resolved when marked. */ protected markAsTreated(fileId: string) : Promise { // Check if it's already marked. return this.isFileTreated(fileId).catch(() => { // Doesn't exist, insert it. return this.appDB.insertRecord(this.SHARED_FILES_TABLE, {id: fileId}); }); } /** * Store a file in a site's shared folder. * * @param {any} entry File entry. * @param {string} [newName] Name of the new file. If not defined, use original file's name. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise}Promise resolved when done. */ storeFileInSite(entry: any, newName?: string, siteId?: string) : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (!entry || !siteId) { return Promise.reject(null); } newName = newName || entry.name; const sharedFilesFolder = this.getSiteSharedFilesDirPath(siteId), newPath = this.textUtils.concatenatePaths(sharedFilesFolder, newName); // Create dir if it doesn't exist already. return this.fileProvider.createDir(sharedFilesFolder).then(() => { return this.fileProvider.moveFile(entry.fullPath, newPath).then((newFile) => { this.eventsProvider.trigger(CoreEventsProvider.FILE_SHARED, {siteId: siteId, name: newName}); return newFile; }); }); } /** * Unmark a file as treated. * * @param {string} fileId File ID. * @return {Promise} Resolved when unmarked. */ protected unmarkAsTreated(fileId: string) : Promise { return this.appDB.deleteRecords(this.SHARED_FILES_TABLE, {id: fileId}); } }