From 91435072844637ba7371ddc5ea12f937a91859e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 7 Feb 2019 10:12:54 +0100 Subject: [PATCH] MOBILE-1979 sync: Sync activity logs --- src/addon/mod/assign/providers/assign-sync.ts | 7 +- src/addon/mod/choice/providers/sync.ts | 14 ++- src/addon/mod/data/providers/sync.ts | 14 ++- src/addon/mod/feedback/providers/sync.ts | 15 ++- .../mod/forum/pages/discussion/discussion.ts | 2 +- src/addon/mod/forum/providers/sync.ts | 15 ++- src/addon/mod/glossary/providers/sync.ts | 15 ++- src/addon/mod/lesson/providers/lesson-sync.ts | 11 +- src/addon/mod/quiz/pages/player/player.ts | 3 +- src/addon/mod/quiz/providers/quiz-sync.ts | 14 ++- src/addon/mod/quiz/providers/quiz.ts | 30 ++--- src/addon/mod/scorm/providers/scorm-sync.ts | 11 +- src/addon/mod/survey/providers/sync.ts | 14 ++- src/addon/mod/wiki/components/index/index.ts | 4 +- src/addon/mod/wiki/providers/wiki-sync.ts | 13 +- src/addon/mod/workshop/providers/sync.ts | 7 +- src/core/course/providers/log-helper.ts | 118 ++++++++++++++---- .../course/providers/sync-cron-handler.ts | 12 +- 18 files changed, 228 insertions(+), 91 deletions(-) diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 96627a1cc..6283bd878 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModAssignProvider } from './assign'; @@ -61,7 +62,8 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { private courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private assignProvider: AddonModAssignProvider, private assignOfflineProvider: AddonModAssignOfflineProvider, private utils: CoreUtilsProvider, private submissionDelegate: AddonModAssignSubmissionDelegate, - private gradesHelper: CoreGradesHelperProvider, timeUtils: CoreTimeUtilsProvider) { + private gradesHelper: CoreGradesHelperProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModAssignSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -202,6 +204,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { return []; })); + // Sync offline logs. + promises.push(this.logHelper.syncIfNeeded(AddonModAssignProvider.COMPONENT, assignId, siteId)); + syncPromise = Promise.all(promises).then((results) => { const submissions = results[0], grades = results[1]; diff --git a/src/addon/mod/choice/providers/sync.ts b/src/addon/mod/choice/providers/sync.ts index 01635c5d4..b260f1ba7 100644 --- a/src/addon/mod/choice/providers/sync.ts +++ b/src/addon/mod/choice/providers/sync.ts @@ -25,6 +25,7 @@ import { AddonModChoiceProvider } from './choice'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncProvider } from '@providers/sync'; /** @@ -40,7 +41,8 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { protected appProvider: CoreAppProvider, private choiceOffline: AddonModChoiceOfflineProvider, private eventsProvider: CoreEventsProvider, private choiceProvider: AddonModChoiceProvider, translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, - courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider) { + courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModChoiceSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -137,10 +139,12 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get offline responses to be sent. - const syncPromise = this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return {}; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModChoiceProvider.COMPONENT, choiceId, siteId).finally(() => { + return this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return {}; + }); }).then((data) => { if (!data.choiceid) { // Nothing to sync. diff --git a/src/addon/mod/data/providers/sync.ts b/src/addon/mod/data/providers/sync.ts index 6e2a90128..22485fa93 100644 --- a/src/addon/mod/data/providers/sync.ts +++ b/src/addon/mod/data/providers/sync.ts @@ -26,6 +26,7 @@ import { AddonModDataHelperProvider } from './helper'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncProvider } from '@providers/sync'; /** @@ -42,7 +43,7 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { private eventsProvider: CoreEventsProvider, private dataProvider: AddonModDataProvider, protected translate: TranslateService, private utils: CoreUtilsProvider, courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, protected textUtils: CoreTextUtilsProvider, timeUtils: CoreTimeUtilsProvider, - private dataHelper: AddonModDataHelperProvider) { + private dataHelper: AddonModDataHelperProvider, private logHelper: CoreCourseLogHelperProvider) { super('AddonModDataSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -149,10 +150,13 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get answers to be sent. - const syncPromise = this.dataOffline.getDatabaseEntries(dataId, siteId).catch(() => { - // No offline data found, return empty object. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModDataProvider.COMPONENT, dataId, siteId).finally(() => { + // Get answers to be sent. + return this.dataOffline.getDatabaseEntries(dataId, siteId).catch(() => { + // No offline data found, return empty object. + return []; + }); }).then((offlineActions) => { if (!offlineActions.length) { // Nothing to sync. diff --git a/src/addon/mod/feedback/providers/sync.ts b/src/addon/mod/feedback/providers/sync.ts index 4aca43570..e9154d724 100644 --- a/src/addon/mod/feedback/providers/sync.ts +++ b/src/addon/mod/feedback/providers/sync.ts @@ -26,6 +26,7 @@ import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreSyncProvider } from '@providers/sync'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * Service to sync feedbacks. @@ -40,7 +41,8 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { protected appProvider: CoreAppProvider, private feedbackOffline: AddonModFeedbackOfflineProvider, private eventsProvider: CoreEventsProvider, private feedbackProvider: AddonModFeedbackProvider, protected translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, - courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider) { + courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModFeedbackSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -144,10 +146,13 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { this.logger.debug(`Try to sync feedback '${feedbackId}' in site ${siteId}'`); - // Get offline responses to be sent. - const syncPromise = this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).catch(() => { - // No offline data found, return empty array. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId).finally(() => { + // Get offline responses to be sent. + return this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).catch(() => { + // No offline data found, return empty array. + return []; + }); }).then((responses) => { if (!responses.length) { // Nothing to sync. diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 7879e8470..ad3da6f2f 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -299,7 +299,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) { // // Add log in Moodle and mark unread posts as readed. - this.forumProvider.logDiscussionView(this.discussionId, this.forumId).catch(() => { + this.forumProvider.logDiscussionView(this.discussionId, this.forumId || -1).catch(() => { // Ignore errors. }).finally(() => { // Trigger mark read posts. diff --git a/src/addon/mod/forum/providers/sync.ts b/src/addon/mod/forum/providers/sync.ts index 76fc0c4ae..ddb33ab61 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; @@ -53,7 +54,8 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { private utils: CoreUtilsProvider, private forumProvider: AddonModForumProvider, private forumHelper: AddonModForumHelperProvider, - private forumOffline: AddonModForumOfflineProvider) { + private forumOffline: AddonModForumOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModForumSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -189,10 +191,13 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get offline responses to be sent. - const syncPromise = this.forumOffline.getNewDiscussions(forumId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModForumProvider.COMPONENT, forumId, siteId).finally(() => { + // Get offline responses to be sent. + return this.forumOffline.getNewDiscussions(forumId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return []; + }); }).then((discussions) => { if (!discussions.length) { // Nothing to sync. diff --git a/src/addon/mod/glossary/providers/sync.ts b/src/addon/mod/glossary/providers/sync.ts index ab748ab9d..af76e67d8 100644 --- a/src/addon/mod/glossary/providers/sync.ts +++ b/src/addon/mod/glossary/providers/sync.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; @@ -52,7 +53,8 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { private utils: CoreUtilsProvider, private glossaryProvider: AddonModGlossaryProvider, private glossaryHelper: AddonModGlossaryHelperProvider, - private glossaryOffline: AddonModGlossaryOfflineProvider) { + private glossaryOffline: AddonModGlossaryOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModGlossarySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -160,10 +162,13 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get offline responses to be sent. - const syncPromise = this.glossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId).finally(() => { + // Get offline responses to be sent. + return this.glossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return []; + }); }).then((entries) => { if (!entries.length) { // Nothing to sync. diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index de7e2ef7c..69ce789f4 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -24,6 +24,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModLessonProvider } from './lesson'; import { AddonModLessonOfflineProvider } from './lesson-offline'; @@ -86,7 +87,8 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private lessonProvider: AddonModLessonProvider, private lessonOfflineProvider: AddonModLessonOfflineProvider, private prefetchHandler: AddonModLessonPrefetchHandler, timeUtils: CoreTimeUtilsProvider, - private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider) { + private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModLessonSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -263,8 +265,11 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync lesson ' + lessonId + ' in site ' + siteId); - // Try to synchronize the attempts first. - syncPromise = this.lessonOfflineProvider.getLessonAttempts(lessonId, siteId).then((attempts) => { + // Sync offline logs. + syncPromise = this.logHelper.syncIfNeeded(AddonModLessonProvider.COMPONENT, lessonId, siteId).finally(() => { + // Try to synchronize the attempts first. + return this.lessonOfflineProvider.getLessonAttempts(lessonId, siteId); + }).then((attempts) => { if (!attempts.length) { return; } else if (!this.appProvider.isOnline()) { diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index feb27bb7f..669445fbc 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -428,8 +428,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { }); // Mark the page as viewed. We'll ignore errors in this call. - this.quizProvider.logViewAttempt(this.attempt.id, this.quizId, page, this.preflightData, this.offline) - .catch((error) => { + this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline).catch((error) => { // Ignore errors. }); diff --git a/src/addon/mod/quiz/providers/quiz-sync.ts b/src/addon/mod/quiz/providers/quiz-sync.ts index 4d00bf7fa..a7ebbdbec 100644 --- a/src/addon/mod/quiz/providers/quiz-sync.ts +++ b/src/addon/mod/quiz/providers/quiz-sync.ts @@ -22,6 +22,7 @@ import { CoreSyncProvider } from '@providers/sync'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; import { CoreSyncBaseProvider } from '@classes/base-sync'; @@ -61,7 +62,7 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private quizProvider: AddonModQuizProvider, private quizOfflineProvider: AddonModQuizOfflineProvider, private prefetchHandler: AddonModQuizPrefetchHandler, private questionProvider: CoreQuestionProvider, - private questionDelegate: CoreQuestionDelegate) { + private questionDelegate: CoreQuestionDelegate, private logHelper: CoreCourseLogHelperProvider) { super('AddonModQuizSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -259,8 +260,11 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync quiz ' + quiz.id + ' in site ' + siteId); - // Get all the offline attempts for the quiz. - syncPromise = this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId).then((attempts) => { + // Sync offline logs. + syncPromise = this.logHelper.syncIfNeeded(AddonModQuizProvider.COMPONENT, quiz.id, siteId).finally(() => { + // Get all the offline attempts for the quiz. + return this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId); + }).then((attempts) => { // Should return 0 or 1 attempt. if (!attempts.length) { return this.finishSync(siteId, quiz, courseId, warnings); @@ -341,8 +345,8 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { // Answers sent, now set the current page if the attempt isn't finished. if (!finish) { - return this.quizProvider.logViewAttempt(onlineAttempt.id, quiz.id, offlineAttempt.currentpage, - preflightData, false).catch(() => { + return this.quizProvider.logViewAttempt(onlineAttempt.id, offlineAttempt.currentpage, preflightData, + false).catch(() => { // Ignore errors. }); } diff --git a/src/addon/mod/quiz/providers/quiz.ts b/src/addon/mod/quiz/providers/quiz.ts index 1777bafdf..551675b22 100644 --- a/src/addon/mod/quiz/providers/quiz.ts +++ b/src/addon/mod/quiz/providers/quiz.ts @@ -1518,31 +1518,31 @@ export class AddonModQuizProvider { } /** - * Report an attempt as being viewed. + * Report an attempt as being viewed. It did not store logs offline because order of the log is important. * * @param {number} attemptId Attempt ID. * @param {number} [page=0] Page number. * @param {any} [preflightData] Preflight required data (like password). * @param {boolean} [offline] Whether attempt is offline. - * @param {number} quizId Quiz ID. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewAttempt(attemptId: number, quizId: number, page: number = 0, preflightData: any = {}, offline?: boolean, - siteId?: string): Promise { - const params = { - attemptid: attemptId, - page: page, - preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value', true) - }, - promises = []; + logViewAttempt(attemptId: number, page: number = 0, preflightData: any = {}, offline?: boolean, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + attemptid: attemptId, + page: page, + preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value', true) + }, + promises = []; - promises.push(this.logHelper.log('mod_quiz_view_attempt', params, AddonModQuizProvider.COMPONENT, quizId, siteId)); - if (offline) { - promises.push(this.quizOfflineProvider.setAttemptCurrentPage(attemptId, page)); - } + promises.push(site.write('mod_quiz_view_attempt', params)); + if (offline) { + promises.push(this.quizOfflineProvider.setAttemptCurrentPage(attemptId, page, site.getId())); + } - return Promise.all(promises); + return Promise.all(promises); + }); } /** diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index e9b5014fb..1bf3ea4cb 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm'; @@ -67,7 +68,8 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider, private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider, - private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider) { + private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -646,8 +648,11 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync SCORM ' + scorm.id + ' in site ' + siteId); - // Get attempts data. We ignore cache for online attempts, so this call will fail if offline or server down. - syncPromise = this.scormProvider.getAttemptCount(scorm.id, false, true, siteId).then((attemptsData) => { + // Sync offline logs. + syncPromise = this.logHelper.syncIfNeeded(AddonModScormProvider.COMPONENT, scorm.id, siteId).finally(() => { + // Get attempts data. We ignore cache for online attempts, so this call will fail if offline or server down. + return this.scormProvider.getAttemptCount(scorm.id, false, true, siteId); + }).then((attemptsData) => { if (!attemptsData.offline || !attemptsData.offline.length) { // Nothing to sync. return this.finishSync(siteId, scorm, warnings, lastOnline, lastOnlineWasFinished); diff --git a/src/addon/mod/survey/providers/sync.ts b/src/addon/mod/survey/providers/sync.ts index 084e05eec..580c24da4 100644 --- a/src/addon/mod/survey/providers/sync.ts +++ b/src/addon/mod/survey/providers/sync.ts @@ -25,6 +25,7 @@ import { AddonModSurveyProvider } from './survey'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncProvider } from '@providers/sync'; /** @@ -40,7 +41,7 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, courseProvider: CoreCourseProvider, private surveyOffline: AddonModSurveyOfflineProvider, private eventsProvider: CoreEventsProvider, private surveyProvider: AddonModSurveyProvider, - private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider) { + private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider, private logHelper: CoreCourseLogHelperProvider) { super('AddonModSurveySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -141,10 +142,13 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { answersSent: false }; - // Get answers to be sent. - const syncPromise = this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return {}; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModSurveyProvider.COMPONENT, surveyId, siteId).finally(() => { + // Get answers to be sent. + return this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return {}; + }); }).then((data) => { if (!data.answers || !data.answers.length) { // Nothing to sync. diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index ee4362456..25bc9af76 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -110,7 +110,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // Ignore errors. }); } else { - this.wikiProvider.logPageView(this.pageId, this.wikiId).catch(() => { + this.wikiProvider.logPageView(this.pageId, this.wiki.id).catch(() => { // Ignore errors. }); } @@ -341,7 +341,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp this.currentPage = data.pageId; this.showLoadingAndFetch(true, false).then(() => { - this.wikiProvider.logPageView(this.currentPage, this.wikiId).catch(() => { + this.wikiProvider.logPageView(this.currentPage, this.wiki.id).catch(() => { // Ignore errors. }); }); diff --git a/src/addon/mod/wiki/providers/wiki-sync.ts b/src/addon/mod/wiki/providers/wiki-sync.ts index 5a2bdc441..cfd37e1a0 100644 --- a/src/addon/mod/wiki/providers/wiki-sync.ts +++ b/src/addon/mod/wiki/providers/wiki-sync.ts @@ -24,6 +24,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModWikiProvider } from './wiki'; import { AddonModWikiOfflineProvider } from './wiki-offline'; @@ -104,7 +105,8 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private wikiProvider: AddonModWikiProvider, private wikiOfflineProvider: AddonModWikiOfflineProvider, - private utils: CoreUtilsProvider, private groupsProvider: CoreGroupsProvider, timeUtils: CoreTimeUtilsProvider) { + private utils: CoreUtilsProvider, private groupsProvider: CoreGroupsProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModWikiSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -336,8 +338,13 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { syncWiki(wikiId: number, courseId?: number, cmId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - // Sync is done at subwiki level, get all the subwikis. - return this.wikiProvider.getSubwikis(wikiId).then((subwikis) => { + // Sync offline logs. + return this.logHelper.syncIfNeeded(AddonModWikiProvider.COMPONENT, wikiId, siteId).catch(() => { + // Ignore errors. + }).then(() => { + // Sync is done at subwiki level, get all the subwikis. + return this.wikiProvider.getSubwikis(wikiId); + }).then((subwikis) => { const promises = [], result: AddonModWikiSyncWikiResult = { warnings: [], diff --git a/src/addon/mod/workshop/providers/sync.ts b/src/addon/mod/workshop/providers/sync.ts index 9b1fc1c02..74f7619cf 100644 --- a/src/addon/mod/workshop/providers/sync.ts +++ b/src/addon/mod/workshop/providers/sync.ts @@ -24,6 +24,7 @@ import { CoreSyncProvider } from '@providers/sync'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModWorkshopProvider } from './workshop'; import { AddonModWorkshopHelperProvider } from './helper'; import { AddonModWorkshopOfflineProvider } from './offline'; @@ -51,7 +52,8 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { private utils: CoreUtilsProvider, private workshopProvider: AddonModWorkshopProvider, private workshopHelper: AddonModWorkshopHelperProvider, - private workshopOffline: AddonModWorkshopOfflineProvider) { + private workshopOffline: AddonModWorkshopOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModWorkshopSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -172,6 +174,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { return []; })); + // Sync offline logs. + syncPromises.push(this.logHelper.syncIfNeeded(AddonModWorkshopProvider.COMPONENT, workshopId, siteId)); + const result = { warnings: [], updated: false diff --git a/src/core/course/providers/log-helper.ts b/src/core/course/providers/log-helper.ts index 0b5a01f50..24bde0ed5 100644 --- a/src/core/course/providers/log-helper.ts +++ b/src/core/course/providers/log-helper.ts @@ -67,7 +67,7 @@ export class CoreCourseLogHelperProvider { * * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when deleted, rejected if failure. */ protected deleteLogs(component: string, componentId: number, siteId?: string): Promise { @@ -78,19 +78,49 @@ export class CoreCourseLogHelperProvider { }); } + /** + * Delete a WS based log. + * + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} ws WS name. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when deleted, rejected if failure. + */ + protected deleteWSLogsByComponent(component: string, componentId: number, ws: string, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().deleteRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + {component: component, componentid: componentId, ws: ws}); + }); + } + /** * Delete the offline saved activity logs using call data. * * @param {string} ws WS name. * @param {any} data Data to send to the WS. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when deleted, rejected if failure. */ protected deleteWSLogs(ws: string, data: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.getDb().deleteRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, - {ws: ws, data: JSON.stringify(data)}); + {ws: ws, data: this.utils.sortAndStringify(data)}); + }); + } + + /** + * Get all the offline saved activity logs. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of offline logs. + */ + protected getAllLogs(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().getAllRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE); }); } @@ -99,7 +129,7 @@ export class CoreCourseLogHelperProvider { * * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the list of offline logs. */ protected getLogs(component: string, componentId: number, siteId?: string): Promise { @@ -117,7 +147,7 @@ export class CoreCourseLogHelperProvider { * @param {any} data Data to send to the WS. * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ log(ws: string, data: any, component: string, componentId: number, siteId?: string): Promise { @@ -142,10 +172,10 @@ export class CoreCourseLogHelperProvider { /** * Perform the log online. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} siteId Site ID. If not defined, current site. - * @return {Promise} Promise resolved when log is successfully submitted. Rejected with object containing + * @param {string} ws WS name. + * @param {any} data Data to send to the WS. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when log is successfully submitted. Rejected with object containing * the error message (if any) and a boolean indicating if the error was returned by WS. */ protected logOnline(ws: string, data: any, siteId?: string): Promise { @@ -158,8 +188,6 @@ export class CoreCourseLogHelperProvider { // Remove all the logs performed. // TODO: Remove this lines when time is accepted in logs. return this.deleteWSLogs(ws, data); - }).catch((error) => { - return Promise.reject(this.utils.createFakeWSError(error)); }); }); } @@ -171,7 +199,7 @@ export class CoreCourseLogHelperProvider { * @param {any} data Data to send to the WS. * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Resolved with the inserted rowId field. */ protected storeOffline(ws: string, data: any, component: string, componentId: number, siteId?: string): @@ -179,7 +207,7 @@ export class CoreCourseLogHelperProvider { return this.sitesProvider.getSite(siteId).then((site) => { const log = { ws: ws, - data: JSON.stringify(data), + data: this.utils.sortAndStringify(data), component: component, componentid: componentId, time: this.timeUtils.timestamp() @@ -189,12 +217,43 @@ export class CoreCourseLogHelperProvider { }); } + /** + * Sync all the offline saved activity logs. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + syncAll(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const siteId = site.getId(); + + return this.getAllLogs(siteId).then((logs) => { + const unique = []; + + // TODO: When time is accepted on log, do not discard same logs. + logs.forEach((log) => { + // Just perform unique syncs. + const found = unique.find((doneLog) => { + return log.component == doneLog.component && log.componentid == doneLog.componentid && + log.ws == doneLog.ws && log.data == doneLog.data; + }); + + if (!found) { + unique.push(log); + } + }); + + return this.syncLogs(unique, siteId); + }); + }); + } + /** * Sync the offline saved activity logs. * * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ syncIfNeeded(component: string, componentId: number, siteId?: string): Promise { @@ -202,24 +261,37 @@ export class CoreCourseLogHelperProvider { const siteId = site.getId(); return this.getLogs(component, componentId, siteId).then((logs) => { - const done = []; + const unique = []; // TODO: When time is accepted on log, do not discard same logs. - return Promise.all(logs.map((log) => { + logs.forEach((log) => { // Just perform unique syncs. - const found = done.find((doneLog) => { + const found = unique.find((doneLog) => { return log.ws == doneLog.ws && log.data == doneLog.data; }); - if (found) { - return Promise.resolve(); + if (!found) { + unique.push(log); } + }); - done.push(log); - - return this.logOnline(log.ws, this.textUtils.parseJSON(log.data), siteId); - })); + return this.syncLogs(unique, siteId); }); }); } + + /** + * Sync and delete given logs. + * + * @param {any[]} logs Array of log objects. + * @param {string} siteId Site Id. + * @return {Promise} Promise resolved when done. + */ + protected syncLogs(logs: any[], siteId: string): Promise { + return Promise.all(logs.map((log) => { + return this.logOnline(log.ws, this.textUtils.parseJSON(log.data), siteId).then(() => { + return this.deleteWSLogsByComponent(log.component, log.componentid, log.ws); + }); + })); + } } diff --git a/src/core/course/providers/sync-cron-handler.ts b/src/core/course/providers/sync-cron-handler.ts index f7bd6cdba..4e85dd398 100644 --- a/src/core/course/providers/sync-cron-handler.ts +++ b/src/core/course/providers/sync-cron-handler.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreCronHandler } from '@providers/cron'; import { CoreCourseSyncProvider } from './sync'; +import { CoreCourseLogHelperProvider } from './log-helper'; /** * Synchronization cron handler. @@ -23,7 +24,7 @@ import { CoreCourseSyncProvider } from './sync'; export class CoreCourseSyncCronHandler implements CoreCronHandler { name = 'CoreCourseSyncCronHandler'; - constructor(private courseSync: CoreCourseSyncProvider) {} + constructor(private courseSync: CoreCourseSyncProvider, private logHelper: CoreCourseLogHelperProvider) {} /** * Execute the process. @@ -33,7 +34,14 @@ export class CoreCourseSyncCronHandler implements CoreCronHandler { * @return {Promise} Promise resolved when done, rejected if failure. */ execute(siteId?: string): Promise { - return this.courseSync.syncAllCourses(siteId); + const promises = []; + // Sync activity logs even if the activity does not have sync handler. + // This will sync all the activity logs even if there's nothing else to sync and also recources. + promises.push(this.logHelper.syncAll(siteId)); + + promises.push(this.courseSync.syncAllCourses(siteId)); + + return Promise.all(promises); } /**