MOBILE-2992 user: Get and sync preferences

main
Albert Gasset 2019-05-10 10:50:11 +02:00
parent b6e15defdd
commit 65055653be
5 changed files with 396 additions and 6 deletions

View File

@ -0,0 +1,114 @@
// (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, CoreSiteSchema } from '@providers/sites';
/**
* Structure of offline user preferences.
*/
export interface CoreUserOfflinePreference {
name: string;
value: string;
onlinevalue: string;
}
/**
* Service to handle offline user preferences.
*/
@Injectable()
export class CoreUserOfflineProvider {
// Variables for database.
static PREFERENCES_TABLE = 'user_preferences';
protected siteSchema: CoreSiteSchema = {
name: 'CoreUserOfflineProvider',
version: 1,
tables: [
{
name: CoreUserOfflineProvider.PREFERENCES_TABLE,
columns: [
{
name: 'name',
type: 'TEXT',
unique: true,
notNull: true
},
{
name: 'value',
type: 'TEXT'
},
{
name: 'onlinevalue',
type: 'TEXT'
},
]
}
]
};
constructor(private sitesProvider: CoreSitesProvider) {
this.sitesProvider.registerSiteSchema(this.siteSchema);
}
/**
* Get preferences that were changed offline.
*
* @return {Promise<CoreUserOfflinePreference[]>} Promise resolved with list of preferences.
*/
getChangedPreferences(siteId?: string): Promise<CoreUserOfflinePreference[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecordsSelect(CoreUserOfflineProvider.PREFERENCES_TABLE, 'value != onlineValue');
});
}
/**
* Get an offline preference.
*
* @param {string} name Name of the preference.
* @return {Promise<CoreUserOfflinePreference>} Promise resolved with the preference, rejected if not found.
*/
getPreference(name: string, siteId?: string): Promise<CoreUserOfflinePreference> {
return this.sitesProvider.getSite(siteId).then((site) => {
const conditions = { name };
return site.getDb().getRecord(CoreUserOfflineProvider.PREFERENCES_TABLE, conditions);
});
}
/**
* Set an offline preference.
*
* @param {string} name Name of the preference.
* @param {string} value Value of the preference.
* @param {string} onlineValue Online value of the preference. If unedfined, preserve previously stored value.
* @return {Promise<CoreUserPreference>} Promise resolved when done.
*/
setPreference(name: string, value: string, onlineValue?: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
let promise: Promise<string>;
if (typeof onlineValue == 'undefined') {
promise = this.getPreference(name, site.id).then((preference) => preference.onlinevalue);
} else {
promise = Promise.resolve(onlineValue);
}
return promise.then((onlineValue) => {
const record = { name, value, onlinevalue: onlineValue };
return site.getDb().insertRecord(CoreUserOfflineProvider.PREFERENCES_TABLE, record);
});
});
}
}

View File

@ -0,0 +1,48 @@
// (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 { CoreCronHandler } from '@providers/cron';
import { CoreUserSyncProvider } from './sync';
/**
* Synchronization cron handler.
*/
@Injectable()
export class CoreUserSyncCronHandler implements CoreCronHandler {
name = 'CoreUserSyncCronHandler';
constructor(private userSync: CoreUserSyncProvider) {}
/**
* Execute the process.
* Receives the ID of the site affected, undefined for all sites.
*
* @param {string} [siteId] ID of the site affected, undefined for all sites.
* @param {boolean} [force] Wether the execution is forced (manual sync).
* @return {Promise<any>} Promise resolved when done, rejected if failure.
*/
execute(siteId?: string, force?: boolean): Promise<any> {
return this.userSync.syncPreferences(siteId);
}
/**
* Get the time between consecutive executions.
*
* @return {number} Time between consecutive executions (in ms).
*/
getInterval(): number {
return 300000; // 5 minutes.
}
}

View File

@ -0,0 +1,100 @@
// (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 { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreAppProvider } from '@providers/app';
import { CoreUserOfflineProvider } from './offline';
import { CoreUserProvider } from './user';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { TranslateService } from '@ngx-translate/core';
import { CoreSyncProvider } from '@providers/sync';
/**
* Service to sync user preferences.
*/
@Injectable()
export class CoreUserSyncProvider extends CoreSyncBaseProvider {
static AUTO_SYNCED = 'core_user_autom_synced';
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
translate: TranslateService, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider,
private userOffline: CoreUserOfflineProvider, private userProvider: CoreUserProvider,
private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider) {
super('CoreUserSync', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils);
}
/**
* Try to synchronize user preferences in a certain site or in all sites.
*
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
syncPreferences(siteId?: string): Promise<any> {
const syncFunctionLog = 'all user preferences';
return this.syncOnSites(syncFunctionLog, this.syncPreferencesFunc.bind(this), [], siteId);
}
/**
* Sync user preferences of a site.
*
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
protected syncPreferencesFunc(siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const syncId = 'preferences';
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing, return the promise.
return this.getOngoingSync(syncId, siteId);
}
const warnings = [];
this.logger.debug(`Try to sync user preferences`);
const syncPromise = this.userOffline.getChangedPreferences(siteId).then((preferences) => {
return this.utils.allPromises(preferences.map((preference) => {
return this.userProvider.getUserPreferenceOnline(preference.name, siteId).then((onlineValue) => {
if (preference.onlinevalue != onlineValue) {
// Prefernce was changed on web while the app was offline, do not sync.
return this.userOffline.setPreference(preference.name, onlineValue, onlineValue, siteId);
}
return this.userProvider.setUserPreference(name, preference.value, siteId).catch((error) => {
if (this.utils.isWebServiceError(error)) {
warnings.push(this.textUtils.getErrorMessageFromError(error));
} else {
// Couldn't connect to server, reject.
return Promise.reject(error);
}
});
});
}));
}).then(() => {
// All done, return the warnings.
return warnings;
});
return this.addOngoingSync(syncId, syncPromise, siteId);
}
}

View File

@ -15,9 +15,11 @@
import { Injectable } from '@angular/core';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSite } from '@classes/site';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreAppProvider } from '@providers/app';
import { CoreUserOfflineProvider } from './offline';
/**
* Service to provide user functionalities.
@ -59,7 +61,8 @@ export class CoreUserProvider {
protected logger;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
private filepoolProvider: CoreFilepoolProvider) {
private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider,
private userOffline: CoreUserOfflineProvider) {
this.logger = logger.getInstance('CoreUserProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema);
}
@ -272,6 +275,67 @@ export class CoreUserProvider {
});
}
/**
* Get a user preference (online or offline).
*
* @param {string} name Name of the preference.
* @param {string} [siteId] Site Id. If not defined, use current site.
* @return {string} Preference value or null if preference not set.
*/
getUserPreference(name: string, siteId?: string): Promise<string> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.userOffline.getPreference(name, siteId).catch(() => {
return null;
}).then((preference) => {
if (preference && !this.appProvider.isOnline()) {
// Offline, return stored value.
return preference.value;
}
return this.getUserPreferenceOnline(name, siteId).then((wsValue) => {
if (preference && preference.value != preference.onlinevalue && preference.onlinevalue == wsValue) {
// Sync is pending for this preference, return stored value.
return preference.value;
}
return this.userOffline.setPreference(name, wsValue, wsValue).then(() => {
return wsValue;
});
});
});
}
/**
* Get cache key for a user preference WS call.
*
* @param {string} name Preference name.
* @return {string} Cache key.
*/
protected getUserPreferenceCacheKey(name: string): string {
return this.ROOT_CACHE_KEY + 'preference:' + name;
}
/**
* Get a user preference online.
*
* @param {string} name Name of the preference.
* @param {string} [siteId] Site Id. If not defined, use current site.
* @return {string} Preference value or null if preference not set.
*/
getUserPreferenceOnline(name: string, siteId?: string): Promise<string> {
return this.sitesProvider.getSite(siteId).then((site) => {
const data = { name };
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getUserPreferenceCacheKey(data.name)
};
return site.read('core_user_get_user_preferences', data, preSets).then((result) => {
return result.preferences[0] ? result.preferences[0].value : null;
});
});
}
/**
* Invalidates user WS calls.
*
@ -298,6 +362,19 @@ export class CoreUserProvider {
});
}
/**
* Invalidate user preference.
*
* @param {string} name Name of the preference.
* @param {string} [siteId] Site Id. If not defined, use current site.
* @return {Promise<any>} Promise resolved when the data is invalidated.
*/
invalidateUserPreference(name: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getUserPreferenceCacheKey(name));
});
}
/**
* Check if course participants is disabled in a certain site.
*
@ -455,6 +532,45 @@ export class CoreUserProvider {
return Promise.all(promises);
}
/**
* Set a user preference (online or offline).
*
* @param {string} name Name of the preference.
* @param {string} value Value of the preference.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved on success.
*/
setUserPreference(name: string, value: string, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
let isOnline = this.appProvider.isOnline();
let promise: Promise<any>;
if (isOnline) {
const preferences = [{type: name, value}];
promise = this.updateUserPreferences(preferences, undefined, undefined, siteId).catch((error) => {
// Preference not saved online.
isOnline = false;
return Promise.reject(error);
});
} else {
promise = Promise.resolve();
}
return promise.finally(() => {
// Update stored online value if saved online.
const onlineValue = isOnline ? value : undefined;
return Promise.all([
this.userOffline.setPreference(name, value, onlineValue),
this.invalidateUserPreference(name).catch(() => {
// Ignore error.
})
]);
});
}
/**
* Update a preference for a user.
*
@ -478,13 +594,14 @@ export class CoreUserProvider {
/**
* Update some preferences for a user.
*
* @param {any} preferences List of preferences.
* @param {{name: string, value: string}[]} preferences List of preferences.
* @param {boolean} [disableNotifications] Whether to disable all notifications. Undefined to not update this value.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if success.
*/
updateUserPreferences(preferences: any, disableNotifications: boolean, userId?: number, siteId?: string): Promise<any> {
updateUserPreferences(preferences: {type: string, value: string}[], disableNotifications?: boolean, userId?: number,
siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();

View File

@ -26,6 +26,10 @@ import { CoreUserParticipantsCourseOptionHandler } from './providers/course-opti
import { CoreUserParticipantsLinkHandler } from './providers/participants-link-handler';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
import { CoreUserComponentsModule } from './components/components.module';
import { CoreCronDelegate } from '@providers/cron';
import { CoreUserOfflineProvider } from './providers/offline';
import { CoreUserSyncProvider } from './providers/sync';
import { CoreUserSyncCronHandler } from './providers/sync-cron-handler';
// List of providers (without handlers).
export const CORE_USER_PROVIDERS: any[] = [
@ -33,6 +37,8 @@ export const CORE_USER_PROVIDERS: any[] = [
CoreUserProfileFieldDelegate,
CoreUserProvider,
CoreUserHelperProvider,
CoreUserOfflineProvider,
CoreUserSyncProvider
];
@NgModule({
@ -46,10 +52,13 @@ export const CORE_USER_PROVIDERS: any[] = [
CoreUserProfileFieldDelegate,
CoreUserProvider,
CoreUserHelperProvider,
CoreUserOfflineProvider,
CoreUserSyncProvider,
CoreUserProfileMailHandler,
CoreUserProfileLinkHandler,
CoreUserParticipantsCourseOptionHandler,
CoreUserParticipantsLinkHandler
CoreUserParticipantsLinkHandler,
CoreUserSyncCronHandler,
]
})
export class CoreUserModule {
@ -57,12 +66,14 @@ export class CoreUserModule {
eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, userProvider: CoreUserProvider,
contentLinksDelegate: CoreContentLinksDelegate, userLinkHandler: CoreUserProfileLinkHandler,
courseOptionHandler: CoreUserParticipantsCourseOptionHandler, linkHandler: CoreUserParticipantsLinkHandler,
courseOptionsDelegate: CoreCourseOptionsDelegate) {
courseOptionsDelegate: CoreCourseOptionsDelegate, cronDelegate: CoreCronDelegate,
syncHandler: CoreUserSyncCronHandler) {
userDelegate.registerHandler(userProfileMailHandler);
courseOptionsDelegate.registerHandler(courseOptionHandler);
contentLinksDelegate.registerHandler(userLinkHandler);
contentLinksDelegate.registerHandler(linkHandler);
cronDelegate.register(syncHandler);
eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => {
// Search for userid in params.