Merge pull request #1230 from dpalou/MOBILE-2355

MOBILE-2355 sync: Implement sync provider and base class
main
Juan Leyva 2018-01-25 15:39:35 +01:00 committed by GitHub
commit b28294a812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 376 additions and 1 deletions

View File

@ -47,6 +47,7 @@ import { CoreFileSessionProvider } from '../providers/file-session';
import { CoreFilepoolProvider } from '../providers/filepool';
import { CoreUpdateManagerProvider } from '../providers/update-manager';
import { CorePluginFileDelegate } from '../providers/plugin-file-delegate';
import { CoreSyncProvider } from '../providers/sync';
// Core modules.
import { CoreComponentsModule } from '../components/components.module';
@ -131,7 +132,8 @@ export function createTranslateLoader(http: HttpClient) {
CoreFileSessionProvider,
CoreFilepoolProvider,
CoreUpdateManagerProvider,
CorePluginFileDelegate
CorePluginFileDelegate,
CoreSyncProvider
]
})
export class AppModule {

View File

@ -0,0 +1,201 @@
// (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 { CoreSitesProvider } from '../providers/sites';
import { CoreSyncProvider } from '../providers/sync';
/**
* Base class to create sync providers. It provides some common functions.
*/
export class CoreSyncBaseProvider {
/**
* Component of the sync provider.
* @type {string}
*/
component = 'core';
/**
* Sync provider's interval.
* @type {number}
*/
syncInterval = 300000;
// Store sync promises.
protected syncPromises: {[siteId: string]: {[uniqueId: string]: Promise<any>}} = {};
constructor(private sitesProvider: CoreSitesProvider) {}
/**
* Add an ongoing sync to the syncPromises list. On finish the promise will be removed.
*
* @param {number} id Unique sync identifier per component.
* @param {Promise<any>} promise The promise of the sync to add.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} The sync promise.
*/
addOngoingSync(id: number, promise: Promise<any>, siteId?: string) : Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const uniqueId = this.getUniqueSyncId(id);
if (!this.syncPromises[siteId]) {
this.syncPromises[siteId] = {};
}
this.syncPromises[siteId][uniqueId] = promise;
// Promise will be deleted when finish.
return promise.finally(() => {
delete this.syncPromises[siteId][uniqueId];
});
}
/**
* If there's an ongoing sync for a certain identifier return it.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise of the current sync or undefined if there isn't any.
*/
getOngoingSync(id: number, siteId?: string) : Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.isSyncing(id, siteId)) {
// There's already a sync ongoing for this discussion, return the promise.
const uniqueId = this.getUniqueSyncId(id);
return this.syncPromises[siteId][uniqueId];
}
}
/**
* Get the synchronization time. Returns 0 if no time stored.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<number>} Promise resolved with the time.
*/
getSyncTime(id: number, siteId?: string) : Promise<number> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getRecord(CoreSyncProvider.SYNC_TABLE, {component: this.component, id: id}).then((entry) => {
return entry.time;
}).catch(() => {
return 0;
});
});
}
/**
* Get the synchronization warnings of an instance.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<string[]>} Promise resolved with the warnings.
*/
getSyncWarnings(id: number, siteId?: string) : Promise<string[]> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getRecord(CoreSyncProvider.SYNC_TABLE, {component: this.component, id: id}).then((entry) => {
try {
return JSON.parse(entry.warnings);
} catch(ex) {
return [];
}
}).catch(() => {
return [];
});
});
}
/**
* Create a unique identifier from component and id.
*
* @param {number} id Unique sync identifier per component.
* @return {string} Unique identifier from component and id.
*/
protected getUniqueSyncId(id: number) : string {
return this.component + '#' + id;
}
/**
* Check if a there's an ongoing syncronization for the given id.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {boolean} Whether it's synchronizing.
*/
isSyncing(id: number, siteId?: string) : boolean {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const uniqueId = this.getUniqueSyncId(id);
return !!(this.syncPromises[siteId] && this.syncPromises[siteId][uniqueId]);
}
/**
* Check if a sync is needed: if a certain time has passed since the last time.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: whether sync is needed.
*/
isSyncNeeded(id: number, siteId?: string) : Promise<boolean> {
return this.getSyncTime(id, siteId).then((time) => {
return Date.now() - this.syncInterval >= time;
});
}
/**
* Set the synchronization time.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [time] Time to set. If not defined, current time.
* @return {Promise<any>} Promise resolved when the time is set.
*/
setSyncTime(id: number, siteId?: string, time?: number) : Promise<any> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
time = typeof time != 'undefined' ? time : Date.now();
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, {time: time}, {component: this.component, id: id});
});
}
/**
* Set the synchronization warnings.
*
* @param {number} id Unique sync identifier per component.
* @param {string[]} warnings Warnings to set.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
setSyncWarnings(id: number, warnings: string[], siteId?: string) : Promise<any> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
warnings = warnings || [];
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, {warnings: JSON.stringify(warnings)},
{component: this.component, id: id});
});
}
/**
* If there's an ongoing sync for a certain identifier, wait for it to end.
* If there's no sync ongoing the promise will be resolved right away.
*
* @param {number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when there's no sync going on for the identifier.
*/
waitForSync(id: number, siteId?: string) : Promise<any> {
const promise = this.getOngoingSync(id, siteId);
if (promise) {
return promise.catch(() => {});
}
return Promise.resolve();
}
}

View File

@ -0,0 +1,172 @@
// (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 { CoreEventsProvider } from './events';
import { CoreSitesProvider } from './sites';
/*
* Service that provides some features regarding synchronization.
*/
@Injectable()
export class CoreSyncProvider {
// Variables for the database.
public static SYNC_TABLE = 'sync';
protected tableSchema = {
name: CoreSyncProvider.SYNC_TABLE,
columns: [
{
name: 'component',
type: 'TEXT',
notNull: true
},
{
name: 'id',
type: 'INTEGER',
notNull: true
},
{
name: 'time',
type: 'INTEGER'
},
{
name: 'warnings',
type: 'TEXT'
}
],
primaryKeys: ['component', 'id']
};
// Store blocked sync objects.
protected blockedItems: {[siteId: string]: {[blockId: string]: {[operation: string]: boolean}}} = {};
constructor(eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider) {
this.sitesProvider.createTableFromSchema(this.tableSchema);
// Unblock all blocks on logout.
eventsProvider.on(CoreEventsProvider.LOGOUT, (data) => {
this.clearAllBlocks(data.siteId);
});
}
/**
* Block a component and ID so it cannot be synchronized.
*
* @param {string} component Component name.
* @param {number} id Unique ID per component.
* @param {string} [operation] Operation name. If not defined, a default text is used.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
blockOperation(component: string, id: number, operation?: string, siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const uniqueId = this.getUniqueSyncBlockId(component, id);
if (!this.blockedItems[siteId]) {
this.blockedItems[siteId] = {};
}
if (!this.blockedItems[siteId][uniqueId]) {
this.blockedItems[siteId][uniqueId] = {};
}
operation = operation || '-';
this.blockedItems[siteId][uniqueId][operation] = true;
}
/**
* Clear all blocks for a site or all sites.
*
* @param {string} [siteId] If set, clear the blocked objects only for this site. Otherwise clear them for all sites.
*/
clearAllBlocks(siteId?: string) : void {
if (siteId) {
delete this.blockedItems[siteId];
} else {
this.blockedItems = {};
}
}
/**
* Clear all blocks for a certain component.
*
* @param {string} component Component name.
* @param {number} id Unique ID per component.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
clearBlocks(component: string, id: number, siteId?: string) : void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const uniqueId = this.getUniqueSyncBlockId(component, id);
if (this.blockedItems[siteId]) {
delete this.blockedItems[siteId][uniqueId];
}
}
/**
* Convenience function to create unique identifiers for a component and id.
*
* @param {string} component Component name.
* @param {number} id Unique ID per component.
* @return {string} Unique sync id.
*/
protected getUniqueSyncBlockId(component: string, id: number) : string {
return component + '#' + id;
}
/**
* Check if a component is blocked.
* One block can have different operations. Here we check how many operations are being blocking the object.
*
* @param {string} component Component name.
* @param {number} id Unique ID per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {boolean} Whether it's blocked.
*/
isBlocked(component: string, id: number, siteId?: string) : boolean {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (!this.blockedItems[siteId]) {
return false;
}
const uniqueId = this.getUniqueSyncBlockId(component, id);
if (!this.blockedItems[siteId][uniqueId]) {
return false;
}
return Object.keys(this.blockedItems[siteId][uniqueId]).length > 0;
}
/**
* Unblock an operation on a component and ID.
*
* @param {string} component Component name.
* @param {number} id Unique ID per component.
* @param {string} [operation] Operation name. If not defined, a default text is used.
* @param {string} [siteId] Site ID. If not defined, current site.
*/
unblockOperation(component: string, id: number, operation?: string, siteId?: string) : void {
operation = operation || '-';
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const uniqueId = this.getUniqueSyncBlockId(component, id);
if (this.blockedItems[siteId] && this.blockedItems[siteId][uniqueId]) {
delete this.blockedItems[siteId][uniqueId][operation];
}
}
}