306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// 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 { CoreSyncBlockedError } from '@classes/base-sync';
|
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
|
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
|
|
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
|
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
|
|
import { CoreApp } from '@services/app';
|
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
|
import { CoreSync } from '@services/sync';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { makeSingleton, Translate } from '@singletons';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import { AddonModFeedback, AddonModFeedbackProvider, AddonModFeedbackWSFeedback } from './feedback';
|
|
import { AddonModFeedbackOffline, AddonModFeedbackOfflineResponse } from './feedback-offline';
|
|
import { AddonModFeedbackPrefetchHandler, AddonModFeedbackPrefetchHandlerService } from './handlers/prefetch';
|
|
|
|
/**
|
|
* Service to sync feedbacks.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModFeedbackSyncResult> {
|
|
|
|
static readonly AUTO_SYNCED = 'addon_mod_feedback_autom_synced';
|
|
|
|
protected componentTranslatableString = 'feedback';
|
|
|
|
constructor() {
|
|
super('AddonModFeedbackSyncProvider');
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
prefetchAfterUpdate(
|
|
prefetchHandler: AddonModFeedbackPrefetchHandlerService,
|
|
module: CoreCourseAnyModuleData,
|
|
courseId: number,
|
|
regex?: RegExp,
|
|
siteId?: string,
|
|
): Promise<void> {
|
|
regex = regex || /^.*files$|^timers/;
|
|
|
|
return super.prefetchAfterUpdate(prefetchHandler, module, courseId, regex, siteId);
|
|
}
|
|
|
|
/**
|
|
* Try to synchronize all the feedbacks in a certain site or in all sites.
|
|
*
|
|
* @param siteId Site ID to sync. If not defined, sync all sites.
|
|
* @param force Wether to force sync not depending on last execution.
|
|
* @return Promise resolved if sync is successful, rejected if sync fails.
|
|
*/
|
|
syncAllFeedbacks(siteId?: string, force?: boolean): Promise<void> {
|
|
return this.syncOnSites('all feedbacks', this.syncAllFeedbacksFunc.bind(this, !!force), siteId);
|
|
}
|
|
|
|
/**
|
|
* Sync all pending feedbacks on a site.
|
|
*
|
|
* @param force Wether to force sync not depending on last execution.
|
|
* @param siteId Site ID to sync. If not defined, sync all sites.
|
|
* @param Promise resolved if sync is successful, rejected if sync fails.
|
|
*/
|
|
protected async syncAllFeedbacksFunc(force: boolean, siteId?: string): Promise<void> {
|
|
// Sync all new responses.
|
|
const responses = await AddonModFeedbackOffline.getAllFeedbackResponses(siteId);
|
|
|
|
// Do not sync same feedback twice.
|
|
const treated: Record<number, boolean> = {};
|
|
|
|
await Promise.all(responses.map(async (response) => {
|
|
if (treated[response.feedbackid]) {
|
|
return;
|
|
}
|
|
|
|
treated[response.feedbackid] = true;
|
|
|
|
const result = force ?
|
|
await this.syncFeedback(response.feedbackid, siteId) :
|
|
await this.syncFeedbackIfNeeded(response.feedbackid, siteId);
|
|
|
|
if (result?.updated) {
|
|
// Sync successful, send event.
|
|
CoreEvents.trigger(AddonModFeedbackSyncProvider.AUTO_SYNCED, {
|
|
feedbackId: response.feedbackid,
|
|
warnings: result.warnings,
|
|
}, siteId);
|
|
}
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Sync a feedback only if a certain time has passed since the last time.
|
|
*
|
|
* @param feedbackId Feedback ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the feedback is synced or if it doesn't need to be synced.
|
|
*/
|
|
async syncFeedbackIfNeeded(feedbackId: number, siteId?: string): Promise<AddonModFeedbackSyncResult | undefined> {
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
const needed = await this.isSyncNeeded(feedbackId, siteId);
|
|
|
|
if (needed) {
|
|
return this.syncFeedback(feedbackId, siteId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Synchronize all offline responses of a feedback.
|
|
*
|
|
* @param feedbackId Feedback ID to be synced.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if sync is successful, rejected otherwise.
|
|
*/
|
|
syncFeedback(feedbackId: number, siteId?: string): Promise<AddonModFeedbackSyncResult> {
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
if (this.isSyncing(feedbackId, siteId)) {
|
|
// There's already a sync ongoing for this feedback, return the promise.
|
|
return this.getOngoingSync(feedbackId, siteId)!;
|
|
}
|
|
|
|
// Verify that feedback isn't blocked.
|
|
if (CoreSync.isBlocked(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId)) {
|
|
this.logger.debug(`Cannot sync feedback '${feedbackId}' because it is blocked.`);
|
|
|
|
throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
|
|
}
|
|
|
|
this.logger.debug(`Try to sync feedback '${feedbackId}' in site ${siteId}'`);
|
|
|
|
return this.addOngoingSync(feedbackId, this.performSyncFeedback(feedbackId, siteId), siteId);
|
|
}
|
|
|
|
/**
|
|
* Perform the feedback sync.
|
|
*
|
|
* @param feedbackId Feedback ID.
|
|
* @param siteId Site ID.
|
|
* @return Promise resolved in success.
|
|
*/
|
|
protected async performSyncFeedback(feedbackId: number, siteId: string): Promise<AddonModFeedbackSyncResult> {
|
|
const result: AddonModFeedbackSyncResult = {
|
|
warnings: [],
|
|
updated: false,
|
|
};
|
|
|
|
// Sync offline logs.
|
|
await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId));
|
|
|
|
// Get offline responses to be sent.
|
|
const responses = await CoreUtils.ignoreErrors(AddonModFeedbackOffline.getFeedbackResponses(feedbackId, siteId));
|
|
|
|
if (!responses || !responses.length) {
|
|
// Nothing to sync.
|
|
await CoreUtils.ignoreErrors(this.setSyncTime(feedbackId, siteId));
|
|
|
|
return result;
|
|
}
|
|
|
|
if (!CoreApp.isOnline()) {
|
|
// Cannot sync in offline.
|
|
throw new CoreNetworkError();
|
|
}
|
|
|
|
const courseId = responses[0].courseid;
|
|
|
|
const feedback = await AddonModFeedback.getFeedbackById(courseId, feedbackId, { siteId });
|
|
|
|
if (!feedback.multiple_submit) {
|
|
// If it does not admit multiple submits, check if it is completed to know if we can submit.
|
|
const isCompleted = await AddonModFeedback.isCompleted(feedbackId, { cmId: feedback.coursemodule, siteId });
|
|
|
|
if (isCompleted) {
|
|
// Cannot submit again, delete resposes.
|
|
await Promise.all(responses.map((data) =>
|
|
AddonModFeedbackOffline.deleteFeedbackPageResponses(feedbackId, data.page, siteId)));
|
|
|
|
result.updated = true;
|
|
this.addOfflineDataDeletedWarning(
|
|
result.warnings,
|
|
feedback.name,
|
|
Translate.instant('addon.mod_feedback.this_feedback_is_already_submitted'),
|
|
);
|
|
|
|
await CoreUtils.ignoreErrors(this.setSyncTime(feedbackId, siteId));
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
const timemodified = await AddonModFeedback.getCurrentCompletedTimeModified(feedbackId, {
|
|
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
|
|
siteId,
|
|
});
|
|
// Sort by page.
|
|
responses.sort((a, b) => a.page - b.page);
|
|
|
|
const orderedData = responses.map((data) => ({
|
|
function: this.processPage.bind(this, feedback, data, siteId, timemodified, result),
|
|
blocking: true,
|
|
}));
|
|
|
|
// Execute all the processes in order to solve dependencies.
|
|
await CoreUtils.executeOrderedPromises(orderedData);
|
|
|
|
if (result.updated) {
|
|
// Data has been sent to server, update data.
|
|
try {
|
|
const module = await CoreCourse.getModuleBasicInfoByInstance(feedbackId, 'feedback', siteId);
|
|
|
|
await this.prefetchAfterUpdate(AddonModFeedbackPrefetchHandler.instance, module, courseId, undefined, siteId);
|
|
} catch {
|
|
// Ignore errors.
|
|
}
|
|
}
|
|
|
|
// Sync finished, set sync time.
|
|
await CoreUtils.ignoreErrors(this.setSyncTime(feedbackId, siteId));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Convenience function to sync process page calls.
|
|
*
|
|
* @param feedback Feedback object.
|
|
* @param data Response data.
|
|
* @param siteId Site Id.
|
|
* @param timemodified Current completed modification time.
|
|
* @param result Result object to be modified.
|
|
* @return Resolve when done or rejected with error.
|
|
*/
|
|
protected async processPage(
|
|
feedback: AddonModFeedbackWSFeedback,
|
|
data: AddonModFeedbackOfflineResponse,
|
|
siteId: string,
|
|
timemodified: number,
|
|
result: AddonModFeedbackSyncResult,
|
|
): Promise<void> {
|
|
// Delete all pages that are submitted before changing website.
|
|
if (timemodified > data.timemodified) {
|
|
return AddonModFeedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
|
|
}
|
|
|
|
try {
|
|
await AddonModFeedback.processPageOnline(feedback.id, data.page, data.responses, false, siteId);
|
|
|
|
result.updated = true;
|
|
|
|
await AddonModFeedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
|
|
} catch (error) {
|
|
if (!CoreUtils.isWebServiceError(error)) {
|
|
// Couldn't connect to server, reject.
|
|
throw error;
|
|
}
|
|
|
|
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
|
|
result.updated = true;
|
|
|
|
await AddonModFeedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
|
|
|
|
// Responses deleted, add a warning.
|
|
this.addOfflineDataDeletedWarning(
|
|
result.warnings,
|
|
feedback.name,
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
export const AddonModFeedbackSync = makeSingleton(AddonModFeedbackSyncProvider);
|
|
|
|
/**
|
|
* Data returned by a feedback sync.
|
|
*/
|
|
export type AddonModFeedbackSyncResult = {
|
|
warnings: string[]; // List of warnings.
|
|
updated: boolean; // Whether some data was sent to the server or offline data was updated.
|
|
};
|
|
|
|
/**
|
|
* Data passed to AUTO_SYNCED event.
|
|
*/
|
|
export type AddonModFeedbackAutoSyncData = {
|
|
feedbackId: number;
|
|
warnings: string[];
|
|
};
|