270 lines
9.4 KiB
TypeScript
270 lines
9.4 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 { CoreSyncBaseProvider } from '@classes/base-sync';
|
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
|
import { CoreCourses } from '@features/courses/services/courses';
|
|
import { CoreNetwork } from '@services/network';
|
|
import { CoreSites } from '@services/sites';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { Translate, makeSingleton } from '@singletons';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import { AddonNotesDBRecord, AddonNotesDeletedDBRecord } from './database/notes';
|
|
import { AddonNotes, AddonNotesCreateNoteData } from './notes';
|
|
import { AddonNotesOffline } from './notes-offline';
|
|
|
|
/**
|
|
* Service to sync notes.
|
|
*/
|
|
@Injectable( { providedIn: 'root' } )
|
|
export class AddonNotesSyncProvider extends CoreSyncBaseProvider<AddonNotesSyncResult> {
|
|
|
|
static readonly AUTO_SYNCED = 'addon_notes_autom_synced';
|
|
|
|
constructor() {
|
|
super('AddonNotesSync');
|
|
}
|
|
|
|
/**
|
|
* Try to synchronize all the notes 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.
|
|
*/
|
|
syncAllNotes(siteId?: string, force?: boolean): Promise<void> {
|
|
return this.syncOnSites('all notes', (siteId) => this.syncAllNotesFunc(!!force, siteId), siteId);
|
|
}
|
|
|
|
/**
|
|
* Synchronize all the notes in a certain site
|
|
*
|
|
* @param force Wether to force sync not depending on last execution.
|
|
* @param siteId Site ID to sync.
|
|
* @return Promise resolved if sync is successful, rejected if sync fails.
|
|
*/
|
|
protected async syncAllNotesFunc(force: boolean, siteId: string): Promise<void> {
|
|
const notesArray = await Promise.all([
|
|
AddonNotesOffline.getAllNotes(siteId),
|
|
AddonNotesOffline.getAllDeletedNotes(siteId),
|
|
]);
|
|
|
|
// Get all the courses to be synced.
|
|
let courseIds: number[] = [];
|
|
notesArray.forEach((notes: (AddonNotesDeletedDBRecord | AddonNotesDBRecord)[]) => {
|
|
courseIds = courseIds.concat(notes.map((note) => note.courseid));
|
|
});
|
|
|
|
CoreUtils.uniqueArray(courseIds);
|
|
|
|
// Sync all courses.
|
|
const promises = courseIds.map(async (courseId) => {
|
|
const result = await (force
|
|
? this.syncNotes(courseId, siteId)
|
|
: this.syncNotesIfNeeded(courseId, siteId));
|
|
|
|
if (result !== undefined) {
|
|
// Sync successful, send event.
|
|
CoreEvents.trigger(AddonNotesSyncProvider.AUTO_SYNCED, {
|
|
courseId,
|
|
warnings: result.warnings,
|
|
}, siteId);
|
|
}
|
|
});
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Sync course notes only if a certain time has passed since the last time.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the notes are synced or if they don't need to be synced.
|
|
*/
|
|
protected async syncNotesIfNeeded(courseId: number, siteId?: string): Promise<AddonNotesSyncResult | undefined> {
|
|
const needed = await this.isSyncNeeded(courseId, siteId);
|
|
|
|
if (needed) {
|
|
return this.syncNotes(courseId, siteId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Synchronize notes of a course.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if sync is successful, rejected otherwise.
|
|
*/
|
|
syncNotes(courseId: number, siteId?: string): Promise<AddonNotesSyncResult> {
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
const currentSyncPromise = this.getOngoingSync(courseId, siteId);
|
|
if (currentSyncPromise) {
|
|
// There's already a sync ongoing for notes, return the promise.
|
|
return currentSyncPromise;
|
|
}
|
|
|
|
this.logger.debug('Try to sync notes for course ' + courseId);
|
|
|
|
const syncPromise = this.performSyncNotes(courseId, siteId);
|
|
|
|
return this.addOngoingSync(courseId, syncPromise, siteId);
|
|
}
|
|
|
|
/**
|
|
* Perform the synchronization of the notes of a course.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID.
|
|
* @return Promise resolved if sync is successful, rejected otherwise.
|
|
*/
|
|
async performSyncNotes(courseId: number, siteId?: string): Promise<AddonNotesSyncResult> {
|
|
const result: AddonNotesSyncResult = {
|
|
warnings: [],
|
|
};
|
|
|
|
// Get offline notes to be sent and deleted.
|
|
const [offlineNotes, deletedNotes] = await Promise.all([
|
|
AddonNotesOffline.getAllNotes(siteId),
|
|
AddonNotesOffline.getAllDeletedNotes(siteId),
|
|
]);
|
|
|
|
if (!offlineNotes.length && !deletedNotes.length) {
|
|
// Nothing to sync.
|
|
return result;
|
|
}
|
|
|
|
if (!CoreNetwork.isOnline()) {
|
|
// Cannot sync in offline.
|
|
throw new CoreNetworkError();
|
|
}
|
|
|
|
const errors: string[] = [];
|
|
const promises: Promise<void>[] = [];
|
|
|
|
// Format the notes to be sent.
|
|
const notesToSend: AddonNotesCreateNoteData[] = offlineNotes.map((note) => ({
|
|
userid: note.userid,
|
|
publishstate: note.publishstate,
|
|
courseid: note.courseid,
|
|
text: note.content,
|
|
format: 1,
|
|
}));
|
|
|
|
// Send the notes.
|
|
promises.push(AddonNotes.addNotesOnline(notesToSend, siteId).then((response) => {
|
|
// Search errors in the response.
|
|
response.forEach((entry) => {
|
|
if (entry.noteid === -1 && entry.errormessage && errors.indexOf(entry.errormessage) == -1) {
|
|
errors.push(entry.errormessage);
|
|
}
|
|
});
|
|
|
|
return;
|
|
}).catch((error) => {
|
|
if (CoreUtils.isWebServiceError(error)) {
|
|
// It's a WebService error, this means the user cannot send notes.
|
|
errors.push(error);
|
|
|
|
return;
|
|
}
|
|
|
|
// Not a WebService error, reject the synchronization to try again.
|
|
throw error;
|
|
}).then(async () => {
|
|
// Notes were sent, delete them from local DB.
|
|
const promises: Promise<void>[] = offlineNotes.map((note) =>
|
|
AddonNotesOffline.deleteOfflineNote(note.userid, note.content, note.created, siteId));
|
|
|
|
await Promise.all(promises);
|
|
|
|
return;
|
|
}));
|
|
|
|
// Format the notes to be sent.
|
|
const notesToDelete = deletedNotes.map((note) => note.noteid);
|
|
|
|
// Delete the notes.
|
|
promises.push(AddonNotes.deleteNotesOnline(notesToDelete, courseId, siteId).catch((error) => {
|
|
if (CoreUtils.isWebServiceError(error)) {
|
|
// It's a WebService error, this means the user cannot send notes.
|
|
errors.push(error);
|
|
|
|
return;
|
|
}
|
|
|
|
// Not a WebService error, reject the synchronization to try again.
|
|
throw error;
|
|
}).then(async () => {
|
|
// Notes were sent, delete them from local DB.
|
|
const promises = notesToDelete.map((noteId) => AddonNotesOffline.undoDeleteNote(noteId, siteId));
|
|
|
|
await Promise.all(promises);
|
|
|
|
return;
|
|
}));
|
|
|
|
await Promise.all(promises);
|
|
|
|
// Fetch the notes from server to be sure they're up to date.
|
|
await CoreUtils.ignoreErrors(AddonNotes.invalidateNotes(courseId, undefined, siteId));
|
|
|
|
await CoreUtils.ignoreErrors(AddonNotes.getNotes(courseId, undefined, false, true, siteId));
|
|
|
|
if (errors && errors.length) {
|
|
// At least an error occurred, get course name and add errors to warnings array.
|
|
const course = await CoreUtils.ignoreErrors(CoreCourses.getUserCourse(courseId, true, siteId), {});
|
|
|
|
result.warnings = errors.map((error) =>
|
|
Translate.instant('addon.notes.warningnotenotsent', {
|
|
course: 'fullname' in course ? course.fullname : courseId,
|
|
error: error,
|
|
}));
|
|
}
|
|
|
|
// All done, return the warnings.
|
|
return result;
|
|
}
|
|
|
|
}
|
|
export const AddonNotesSync = makeSingleton(AddonNotesSyncProvider);
|
|
|
|
export type AddonNotesSyncResult = {
|
|
warnings: string[]; // List of warnings.
|
|
};
|
|
|
|
/**
|
|
* Data passed to AUTO_SYNCED event.
|
|
*/
|
|
export type AddonNotesSyncAutoSyncData = {
|
|
courseId: number;
|
|
warnings: string[];
|
|
};
|
|
|
|
declare module '@singletons/events' {
|
|
|
|
/**
|
|
* Augment CoreEventsData interface with events specific to this service.
|
|
*
|
|
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
|
*/
|
|
export interface CoreEventsData {
|
|
[AddonNotesSyncProvider.AUTO_SYNCED]: AddonNotesSyncAutoSyncData;
|
|
}
|
|
|
|
}
|