MOBILE-2261 core: Implement filepool and file session

main
Dani Palou 2017-11-20 16:19:21 +01:00
parent 39a1623f8d
commit e216341838
9 changed files with 2917 additions and 11 deletions

View File

@ -46,6 +46,8 @@ import { CoreSitesProvider } from '../providers/sites';
import { CoreLocalNotificationsProvider } from '../providers/local-notifications'; import { CoreLocalNotificationsProvider } from '../providers/local-notifications';
import { CoreGroupsProvider } from '../providers/groups'; import { CoreGroupsProvider } from '../providers/groups';
import { CoreCronDelegate } from '../providers/cron'; import { CoreCronDelegate } from '../providers/cron';
import { CoreFileSessionProvider } from '../providers/file-session';
import { CoreFilepoolProvider } from '../providers/filepool';
// For translate loader. AoT requires an exported function for factories. // For translate loader. AoT requires an exported function for factories.
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
@ -99,6 +101,8 @@ export function createTranslateLoader(http: HttpClient) {
CoreLocalNotificationsProvider, CoreLocalNotificationsProvider,
CoreGroupsProvider, CoreGroupsProvider,
CoreCronDelegate, CoreCronDelegate,
CoreFileSessionProvider,
CoreFilepoolProvider,
] ]
}) })
export class AppModule { export class AppModule {

View File

@ -56,6 +56,8 @@ export interface LocalMobileResponse {
/** /**
* Class that represents a site (combination of site + user). * Class that represents a site (combination of site + user).
* It will have all the site data and provide utility functions regarding a site. * It will have all the site data and provide utility functions regarding a site.
* To add tables to the site's database, please use CoreSitesProvider.createTablesFromSchema. This will make sure that
* the tables are created in all the sites, not just the current one.
*/ */
export class CoreSite { export class CoreSite {
// List of injected services. This class isn't injectable, so it cannot use DI. // List of injected services. This class isn't injectable, so it cannot use DI.

View File

@ -101,7 +101,7 @@ export class SQLiteDB {
columnsSql.push(columnSql); columnsSql.push(columnSql);
} }
sql += columnsSql.join(', ') + ')'; sql += columnsSql.join(', ');
// Now add the table constraints. // Now add the table constraints.
@ -141,7 +141,7 @@ export class SQLiteDB {
} }
} }
return sql; return sql + ')';
} }
/** /**
@ -240,9 +240,9 @@ export class SQLiteDB {
} }
/** /**
* Create a table if it doesn't exist from a schema. * Create several tables if they don't exist from a list of schemas.
* *
* @param {any} table Table schema. * @param {any[]} tables List of table schema.
* @return {Promise<any>} Promise resolved when success. * @return {Promise<any>} Promise resolved when success.
*/ */
createTablesFromSchema(tables: any[]) : Promise<any> { createTablesFromSchema(tables: any[]) : Promise<any> {
@ -394,7 +394,7 @@ export class SQLiteDB {
* @param {any} items A single value or array of values for the expression. It doesn't accept objects. * @param {any} items A single value or array of values for the expression. It doesn't accept objects.
* @param {boolean} [equal=true] True means we want to equate to the constructed expression. * @param {boolean} [equal=true] True means we want to equate to the constructed expression.
* @param {any} [onEmptyItems] This defines the behavior when the array of items provided is empty. Defaults to false, * @param {any} [onEmptyItems] This defines the behavior when the array of items provided is empty. Defaults to false,
* meaning throw exceptions. Other values will become part of the returned SQL fragment. * meaning return empty. Other values will become part of the returned SQL fragment.
* @return {any[]} A list containing the constructed sql fragment and an array of parameters. * @return {any[]} A list containing the constructed sql fragment and an array of parameters.
*/ */
getInOrEqual(items: any, equal=true, onEmptyItems?: any) : any[] { getInOrEqual(items: any, equal=true, onEmptyItems?: any) : any[] {
@ -774,7 +774,7 @@ export class SQLiteDB {
sql, sql,
params; params;
for (var key in data) { for (let key in data) {
sets.push(`${key} = ?`); sets.push(`${key} = ?`);
} }
@ -785,6 +785,43 @@ export class SQLiteDB {
return this.execute(sql, params); return this.execute(sql, params);
} }
/**
* Update one or more records in a table. It accepts a WHERE clause as a string.
*
* @param {string} string table The database table to update.
* @param {any} data An object with the fields to update: fieldname=>fieldvalue.
* @param {string} [where] Where clause. Must not include the "WHERE" word.
* @param {any[]} [whereParams] Params for the where clause.
* @return {Promise<any>} Promise resolved when updated.
*/
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]) : Promise<any> {
if (!data || !Object.keys(data).length) {
// No fields to update, consider it's done.
return Promise.resolve();
}
let sets = [],
sql,
params;
for (let key in data) {
sets.push(`${key} = ?`);
}
sql = `UPDATE ${table} SET ${sets.join(', ')}`;
if (where) {
sql += ` WHERE ${where}`;
}
// Create the list of params using the "data" object and the params for the where clause.
params = Object.keys(data).map(key => data[key]);
if (where && whereParams) {
params = params.concat(whereParams[1]);
}
return this.execute(sql, params);
}
/** /**
* Returns the SQL WHERE conditions. * Returns the SQL WHERE conditions.
* *

View File

@ -37,4 +37,11 @@ export class CoreConstants {
public static loginSSOCode = 2; // SSO in browser window is required. public static loginSSOCode = 2; // SSO in browser window is required.
public static loginSSOInAppCode = 3; // SSO in embedded browser is required. public static loginSSOInAppCode = 3; // SSO in embedded browser is required.
public static loginLaunchData = 'mmLoginLaunchData'; public static loginLaunchData = 'mmLoginLaunchData';
// Download status constants.
public static downloaded = 'downloaded';
public static downloading = 'downloading';
public static notDownloaded = 'notdownloaded';
public static outdated = 'outdated';
public static notDownloadable = 'notdownloadable';
} }

View File

@ -91,7 +91,9 @@ export class SQLiteDBMock extends SQLiteDB {
this.db.transaction((tx) => { this.db.transaction((tx) => {
tx.executeSql(sql, params, (tx, results) => { tx.executeSql(sql, params, (tx, results) => {
resolve(results); resolve(results);
}, reject); }, (tx, error) => {
reject(error);
});
}); });
}); });
} }

View File

@ -0,0 +1,147 @@
// (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 { CoreSitesProvider } from './sites';
/**
* Helper to store some temporary data for file submission.
*
* It uses siteId and component name to index the files.
* Every component can provide a File area identifier to indentify every file list on the session.
* This value can be the activity id or a mix of name and numbers.
*/
@Injectable()
export class CoreFileSessionProvider {
protected files = {};
constructor(private sitesProvider: CoreSitesProvider) {}
/**
* Add a file to the session.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {any} file File to add.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
addFile(component: string, id: string|number, file: any, siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
this.initFileArea(component, id, siteId);
this.files[siteId][component][id].push(file);
}
/**
* Clear files stored in session.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
clearFiles(component: string, id: string|number, siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) {
this.files[siteId][component][id] = [];
}
}
/**
* Get files stored in session.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {any[]} Array of files in session.
*/
getFiles(component: string, id: string|number, siteId?: string) : any[] {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) {
return this.files[siteId][component][id];
}
return [];
}
/**
* Initializes the filearea to store the file.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
protected initFileArea(component: string, id: string|number, siteId?: string) : void {
if (!this.files[siteId]) {
this.files[siteId] = {};
}
if (!this.files[siteId][component]) {
this.files[siteId][component] = {};
}
if (!this.files[siteId][component][id]) {
this.files[siteId][component][id] = [];
}
}
/**
* Remove a file stored in session.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {any} file File to remove. The instance should be exactly the same as the one stored in session.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
removeFile(component: string, id: string|number, file: any, siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) {
const position = this.files[siteId][component][id].indexOf(file);
if (position != -1) {
this.files[siteId][component][id].splice(position, 1);
}
}
}
/**
* Remove a file stored in session.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {number} index Position of the file to remove.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
removeFileByIndex(component: string, id: string|number, index: number, siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id] && index >= 0 &&
index < this.files[siteId][component][id].length) {
this.files[siteId][component][id].splice(index, 1);
}
}
/**
* Set a group of files in the session.
*
* @param {string} component Component Name.
* @param {string|number} id File area identifier.
* @param {any[]} newFiles Files to set.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
setFiles(component: string, id: string|number, newFiles: any[], siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
this.initFileArea(component, id, siteId);
this.files[siteId][component][id] = newFiles;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -51,13 +51,21 @@ export interface CoreSiteBasicInfo {
/* /*
* Service to manage and interact with sites. * Service to manage and interact with sites.
* It allows creating tables in the databases of all sites. Each service or component should be responsible of creating
* their own database tables. Example:
*
* constructor(sitesProvider: CoreSitesProvider) {
* this.sitesProvider.createTableFromSchema(this.tableSchema);
*
* This provider will automatically create the tables in the databases of all the instantiated sites, and also to the
* databases of sites instantiated from now on.
*/ */
@Injectable() @Injectable()
export class CoreSitesProvider { export class CoreSitesProvider {
// Variables for the database. // Variables for the database.
protected SITES_TABLE = 'sites'; protected SITES_TABLE = 'sites';
protected CURRENT_SITE_TABLE = 'current_site'; protected CURRENT_SITE_TABLE = 'current_site';
protected tablesSchema = [ protected appTablesSchema = [
{ {
name: this.SITES_TABLE, name: this.SITES_TABLE,
columns: [ columns: [
@ -122,6 +130,7 @@ export class CoreSitesProvider {
protected currentSite: CoreSite; protected currentSite: CoreSite;
protected sites: {[s: string]: CoreSite} = {}; protected sites: {[s: string]: CoreSite} = {};
protected appDB: SQLiteDB; protected appDB: SQLiteDB;
protected siteTablesSchemas = []; // Schemas for site tables. Other providers can add schemas in here.
constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider, constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider,
private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private translate: TranslateService, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private translate: TranslateService,
@ -129,7 +138,7 @@ export class CoreSitesProvider {
this.logger = logger.getInstance('CoreSitesProvider'); this.logger = logger.getInstance('CoreSitesProvider');
this.appDB = appProvider.getDB(); this.appDB = appProvider.getDB();
this.appDB.createTablesFromSchema(this.tablesSchema); this.appDB.createTablesFromSchema(this.appTablesSchema);
} }
/** /**
@ -383,10 +392,16 @@ export class CoreSitesProvider {
this.addSite(siteId, siteUrl, token, info, privateToken, config); this.addSite(siteId, siteUrl, token, info, privateToken, config);
// Turn candidate site into current site. // Turn candidate site into current site.
this.currentSite = candidateSite; this.currentSite = candidateSite;
this.sites[siteId] = candidateSite;
// Store session. // Store session.
this.login(siteId); this.login(siteId);
this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, siteId); this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, siteId);
if (this.siteTablesSchemas.length) {
// Create tables in the site's database.
candidateSite.getDb().createTablesFromSchema(this.siteTablesSchemas);
}
return siteId; return siteId;
}); });
} else if (result == this.LEGACY_APP_VERSION) { } else if (result == this.LEGACY_APP_VERSION) {
@ -680,6 +695,11 @@ export class CoreSitesProvider {
site = this.sitesFactory.makeSite(entry.id, entry.siteUrl, entry.token, site = this.sitesFactory.makeSite(entry.id, entry.siteUrl, entry.token,
info, entry.privateToken, config, entry.loggedOut == 1); info, entry.privateToken, config, entry.loggedOut == 1);
this.sites[entry.id] = site; this.sites[entry.id] = site;
if (this.siteTablesSchemas.length) {
// Create tables in the site's database.
site.getDb().createTablesFromSchema(this.siteTablesSchemas);
}
return site; return site;
} }
@ -1029,4 +1049,28 @@ export class CoreSitesProvider {
return site.isFeatureDisabled(name); return site.isFeatureDisabled(name);
}); });
} }
/**
* Create a table in all the sites databases.
*
* @param {any} table Table schema.
*/
createTableFromSchema(table: any) : void {
this.createTablesFromSchema([table]);
}
/**
* Create several tables in all the sites databases.
*
* @param {any[]} tables List of tables schema.
*/
createTablesFromSchema(tables: any[]) : void {
// Add the tables to the list of schemas. This list is to create all the tables in new sites.
this.siteTablesSchemas = this.siteTablesSchemas.concat(tables);
// Now create these tables in current sites.
for (let id in this.sites) {
this.sites[id].getDb().createTablesFromSchema(tables);
}
}
} }

View File

@ -46,9 +46,9 @@ export class CoreUrlUtilsProvider {
* Extracts the parameters from a URL and stores them in an object. * Extracts the parameters from a URL and stores them in an object.
* *
* @param {string} url URL to treat. * @param {string} url URL to treat.
* @return {object} Object with the params. * @return {any} Object with the params.
*/ */
extractUrlParams(url: string) : object { extractUrlParams(url: string) : any {
let regex = /[?&]+([^=&]+)=?([^&]*)?/gi, let regex = /[?&]+([^=&]+)=?([^&]*)?/gi,
params = {}; params = {};