diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index fefb0c635..0866ee171 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -20,6 +20,7 @@ import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; import { CoreInitDelegate } from '@providers/init'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_CALENDAR_PROVIDERS: any[] = [ @@ -39,7 +40,7 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [ export class AddonCalendarModule { constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler, initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider, - localNotificationsProvider: CoreLocalNotificationsProvider) { + localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider) { mainMenuDelegate.registerHandler(calendarHandler); initDelegate.ready().then(() => { @@ -60,5 +61,15 @@ export class AddonCalendarModule { }); } }); + + // Allow migrating the table from the old app to the new schema. + // In the old app some calculated properties were stored when it shouldn't. Filter only the fields we want. + updateManager.registerSiteTableMigration({ + name: 'calendar_events', + newName: AddonCalendarProvider.EVENTS_TABLE, + filterFields: ['id', 'name', 'description', 'format', 'eventtype', 'courseid', 'timestart', 'timeduration', + 'categoryid', 'groupid', 'userid', 'instance', 'modulename', 'timemodified', 'repeatid', 'visible', 'uuid', + 'sequence', 'subscriptionid', 'notificationtime'] + }); } } diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index 210923dae..226fde369 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -36,10 +36,10 @@ export class AddonCalendarProvider { protected DEFAULT_NOTIFICATION_TIME = 60; // Variables for database. - protected EVENTS_TABLE = 'calendar_events'; + static EVENTS_TABLE = 'addon_calendar_events'; protected tablesSchema = [ { - name: this.EVENTS_TABLE, + name: AddonCalendarProvider.EVENTS_TABLE, columns: [ { name: 'id', @@ -59,6 +59,10 @@ export class AddonCalendarProvider { name: 'description', type: 'TEXT' }, + { + name: 'format', + type: 'INTEGER' + }, { name: 'eventtype', type: 'TEXT' @@ -83,6 +87,10 @@ export class AddonCalendarProvider { name: 'groupid', type: 'INTEGER' }, + { + name: 'userid', + type: 'INTEGER' + }, { name: 'instance', type: 'INTEGER' @@ -98,6 +106,22 @@ export class AddonCalendarProvider { { name: 'repeatid', type: 'INTEGER' + }, + { + name: 'visible', + type: 'INTEGER' + }, + { + name: 'uuid', + type: 'TEXT' + }, + { + name: 'sequence', + type: 'INTEGER' + }, + { + name: 'subscriptionid', + type: 'INTEGER' } ] } @@ -181,7 +205,7 @@ export class AddonCalendarProvider { */ getEventFromLocalDb(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.EVENTS_TABLE, { id: id }); + return site.getDb().getRecord(AddonCalendarProvider.EVENTS_TABLE, { id: id }); }); } @@ -286,7 +310,7 @@ export class AddonCalendarProvider { * @return {string} Prefix Cache key. */ protected getEventsListPrefixCacheKey(): string { - return this.ROOT_CACHE_KEY + 'eventslist:'; + return this.ROOT_CACHE_KEY + 'events:'; } /** @@ -501,20 +525,26 @@ export class AddonCalendarProvider { id: event.id, name: event.name, description: event.description, + format: event.format, eventtype: event.eventtype, courseid: event.courseid, timestart: event.timestart, timeduration: event.timeduration, categoryid: event.categoryid, groupid: event.groupid, + userid: event.userid, instance: event.instance, modulename: event.modulename, timemodified: event.timemodified, repeatid: event.repeatid, + visible: event.visible, + uuid: event.uuid, + sequence: event.sequence, + subscriptionid: event.subscriptionid, notificationtime: e.notificationtime || -1 }; - return db.insertRecord(this.EVENTS_TABLE, eventRecord); + return db.insertRecord(AddonCalendarProvider.EVENTS_TABLE, eventRecord); })); }); @@ -539,7 +569,7 @@ export class AddonCalendarProvider { event.notificationtime = time; - return site.getDb().insertRecord(this.EVENTS_TABLE, event).then(() => { + return site.getDb().insertRecord(AddonCalendarProvider.EVENTS_TABLE, event).then(() => { return this.scheduleEventNotification(event, time); }); }); diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index 1517905ba..0a202a894 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -36,6 +36,7 @@ import { CoreSettingsDelegate } from '@core/settings/providers/delegate'; import { AddonMessagesSettingsHandler } from './providers/settings-handler'; import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MESSAGES_PROVIDERS: any[] = [ @@ -67,7 +68,7 @@ export class AddonMessagesModule { userDelegate: CoreUserDelegate, cronDelegate: CoreCronDelegate, syncHandler: AddonMessagesSyncCronHandler, network: Network, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider, localNotifications: CoreLocalNotificationsProvider, messagesProvider: AddonMessagesProvider, - sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider, + sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider, updateManager: CoreUpdateManagerProvider, settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate, pushNotificationsDelegate: AddonPushNotificationsDelegate, utils: CoreUtilsProvider, addContactHandler: AddonMessagesAddContactUserHandler, blockContactHandler: AddonMessagesBlockContactUserHandler) { @@ -115,5 +116,17 @@ export class AddonMessagesModule { return true; } }); + + // Allow migrating the table from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_messages_offline_messages', + newName: AddonMessagesOfflineProvider.MESSAGES_TABLE, + fields: [ + { + name: 'textformat', + delete: true + } + ] + }); } } diff --git a/src/addon/messages/providers/messages-offline.ts b/src/addon/messages/providers/messages-offline.ts index a2243f6b9..5bf12837c 100644 --- a/src/addon/messages/providers/messages-offline.ts +++ b/src/addon/messages/providers/messages-offline.ts @@ -26,10 +26,10 @@ export class AddonMessagesOfflineProvider { protected logger; // Variables for database. - protected MESSAGES_TABLE = 'addon_messages_offline_messages'; + static MESSAGES_TABLE = 'addon_messages_offline_messages'; protected tablesSchema = [ { - name: this.MESSAGES_TABLE, + name: AddonMessagesOfflineProvider.MESSAGES_TABLE, columns: [ { name: 'touserid', @@ -72,7 +72,7 @@ export class AddonMessagesOfflineProvider { */ deleteMessage(toUserId: number, message: string, timeCreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.MESSAGES_TABLE, { + return site.getDb().deleteRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, { touserid: toUserId, smallmessage: message, timecreated: timeCreated @@ -88,7 +88,7 @@ export class AddonMessagesOfflineProvider { */ getAllDeviceOfflineMessages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.MESSAGES_TABLE, {deviceoffline: 1}); + return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {deviceoffline: 1}); }); } @@ -101,7 +101,7 @@ export class AddonMessagesOfflineProvider { */ getMessages(toUserId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.MESSAGES_TABLE, {touserid: toUserId}); + return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {touserid: toUserId}); }); } @@ -113,7 +113,7 @@ export class AddonMessagesOfflineProvider { */ getAllMessages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getAllRecords(this.MESSAGES_TABLE); + return site.getDb().getAllRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE); }); } @@ -148,7 +148,7 @@ export class AddonMessagesOfflineProvider { deviceoffline: this.appProvider.isOnline() ? 0 : 1 }; - return site.getDb().insertRecord(this.MESSAGES_TABLE, entry).then(() => { + return site.getDb().insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, entry).then(() => { return entry; }); }); @@ -169,7 +169,7 @@ export class AddonMessagesOfflineProvider { data = { deviceoffline: value ? 1 : 0 }; messages.forEach((message) => { - promises.push(db.insertRecord(this.MESSAGES_TABLE, data)); + promises.push(db.insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, data)); }); return Promise.all(promises); diff --git a/src/addon/mod/assign/assign.module.ts b/src/addon/mod/assign/assign.module.ts index 961dd18b5..5a8045af7 100644 --- a/src/addon/mod/assign/assign.module.ts +++ b/src/addon/mod/assign/assign.module.ts @@ -29,6 +29,7 @@ import { AddonModAssignPrefetchHandler } from './providers/prefetch-handler'; import { AddonModAssignSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModAssignSubmissionModule } from './submission/submission.module'; import { AddonModAssignFeedbackModule } from './feedback/feedback.module'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_ASSIGN_PROVIDERS: any[] = [ @@ -58,9 +59,61 @@ export const ADDON_MOD_ASSIGN_PROVIDERS: any[] = [ export class AddonModAssignModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModAssignModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModAssignPrefetchHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModAssignSyncCronHandler) { + cronDelegate: CoreCronDelegate, syncHandler: AddonModAssignSyncCronHandler, updateManager: CoreUpdateManagerProvider) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); cronDelegate.register(syncHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTablesMigration([ + { + name: 'mma_mod_assign_submissions', + newName: AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, + fields: [ + { + name: 'assignmentid', + newName: 'assignid' + }, + { + name: 'submitted', + type: 'boolean' + }, + { + name: 'submissionstatement', + type: 'boolean' + }, + { + name: 'plugindata', + type: 'object' + } + ] + }, + { + name: 'mma_mod_assign_submissions_grading', + newName: AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, + fields: [ + { + name: 'assignmentid', + newName: 'assignid' + }, + { + name: 'addattempt', + type: 'boolean' + }, + { + name: 'applytoall', + type: 'boolean' + }, + { + name: 'outcomes', + type: 'object' + }, + { + name: 'plugindata', + type: 'object' + } + ] + } + ]); } } diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index 89a9d34c1..f24ebfb12 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -384,7 +384,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { // Check if there's any offline data for this submission. promises.push(this.assignOfflineProvider.getSubmission(assign.id, this.submitId).then((data) => { - this.hasOffline = data && data.pluginData && Object.keys(data.pluginData).length > 0; + this.hasOffline = data && data.plugindata && Object.keys(data.plugindata).length > 0; this.submittedOffline = data && data.submitted; }).catch(() => { // No offline data found. @@ -561,8 +561,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { this.originalGrades.grade = this.grade.grade; } - this.grade.applyToAll = data.applyToAll; - this.grade.addAttempt = data.addAttempt; + this.grade.applyToAll = data.applytoall; + this.grade.addAttempt = data.addattempt; this.originalGrades.applyToAll = this.grade.applyToAll; this.originalGrades.addAttempt = this.grade.addAttempt; diff --git a/src/addon/mod/assign/feedback/comments/component/comments.ts b/src/addon/mod/assign/feedback/comments/component/comments.ts index 0460e0c81..5c821ce09 100644 --- a/src/addon/mod/assign/feedback/comments/component/comments.ts +++ b/src/addon/mod/assign/feedback/comments/component/comments.ts @@ -128,13 +128,13 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb return this.assignOfflineProvider.getSubmissionGrade(this.assign.id, this.userId).catch(() => { // No offline data found. }).then((offlineData) => { - if (offlineData && offlineData.pluginData && offlineData.pluginData.assignfeedbackcomments_editor) { + if (offlineData && offlineData.plugindata && offlineData.plugindata.assignfeedbackcomments_editor) { // Save offline as draft. this.isSent = false; this.feedbackDelegate.saveFeedbackDraft(this.assign.id, this.userId, this.plugin, - offlineData.pluginData.assignfeedbackcomments_editor); + offlineData.plugindata.assignfeedbackcomments_editor); - return offlineData.pluginData.assignfeedbackcomments_editor.text; + return offlineData.plugindata.assignfeedbackcomments_editor.text; } // No offline data found, return online text. diff --git a/src/addon/mod/assign/feedback/comments/providers/handler.ts b/src/addon/mod/assign/feedback/comments/providers/handler.ts index 3b2804a3e..713167bc0 100644 --- a/src/addon/mod/assign/feedback/comments/providers/handler.ts +++ b/src/addon/mod/assign/feedback/comments/providers/handler.ts @@ -128,8 +128,8 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed return this.assignOfflineProvider.getSubmissionGrade(assign.id, userId).catch(() => { // No offline data found. }).then((data) => { - if (data && data.pluginData && data.pluginData.assignfeedbackcomments_editor) { - return data.pluginData.assignfeedbackcomments_editor.text; + if (data && data.plugindata && data.plugindata.assignfeedbackcomments_editor) { + return data.plugindata.assignfeedbackcomments_editor.text; } // No offline data found, get text from plugin. diff --git a/src/addon/mod/assign/pages/edit/edit.ts b/src/addon/mod/assign/pages/edit/edit.ts index 21ca6e70a..e98d6abf0 100644 --- a/src/addon/mod/assign/pages/edit/edit.ts +++ b/src/addon/mod/assign/pages/edit/edit.ts @@ -156,7 +156,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { // Check if there's any offline data for this submission. return this.assignOfflineProvider.getSubmission(this.assign.id, this.userId).then((data) => { - this.hasOffline = data && data.pluginData && Object.keys(data.pluginData).length > 0; + this.hasOffline = data && data.plugindata && Object.keys(data.plugindata).length > 0; }).catch(() => { // No offline data found. this.hasOffline = false; diff --git a/src/addon/mod/assign/providers/assign-offline.ts b/src/addon/mod/assign/providers/assign-offline.ts index c79a4aa30..6c54503b7 100644 --- a/src/addon/mod/assign/providers/assign-offline.ts +++ b/src/addon/mod/assign/providers/assign-offline.ts @@ -28,30 +28,30 @@ export class AddonModAssignOfflineProvider { protected logger; // Variables for database. - protected SUBMISSIONS_TABLE = 'addon_mod_assign_submissions'; - protected SUBMISSIONS_GRADES_TABLE = 'addon_mod_assign_submissions_grading'; + static SUBMISSIONS_TABLE = 'addon_mod_assign_submissions'; + static SUBMISSIONS_GRADES_TABLE = 'addon_mod_assign_submissions_grading'; protected tablesSchema = [ { - name: this.SUBMISSIONS_TABLE, + name: AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, columns: [ { - name: 'assignId', + name: 'assignid', type: 'INTEGER' }, { - name: 'courseId', + name: 'courseid', type: 'INTEGER' }, { - name: 'userId', + name: 'userid', type: 'INTEGER' }, { - name: 'pluginData', + name: 'plugindata', type: 'TEXT' }, { - name: 'onlineTimemodified', + name: 'onlinetimemodified', type: 'INTEGER' }, { @@ -67,25 +67,25 @@ export class AddonModAssignOfflineProvider { type: 'INTEGER' }, { - name: 'submissionStatement', + name: 'submissionstatement', type: 'INTEGER' } ], - primaryKeys: ['assignId', 'userId'] + primaryKeys: ['assignid', 'userid'] }, { - name: this.SUBMISSIONS_GRADES_TABLE, + name: AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, columns: [ { - name: 'assignId', + name: 'assignid', type: 'INTEGER' }, { - name: 'courseId', + name: 'courseid', type: 'INTEGER' }, { - name: 'userId', + name: 'userid', type: 'INTEGER' }, { @@ -93,19 +93,19 @@ export class AddonModAssignOfflineProvider { type: 'REAL' }, { - name: 'attemptNumber', + name: 'attemptnumber', type: 'INTEGER' }, { - name: 'addAttempt', + name: 'addattempt', type: 'INTEGER' }, { - name: 'workflowState', + name: 'workflowstate', type: 'TEXT' }, { - name: 'applyToAll', + name: 'applytoall', type: 'INTEGER' }, { @@ -113,7 +113,7 @@ export class AddonModAssignOfflineProvider { type: 'TEXT' }, { - name: 'pluginData', + name: 'plugindata', type: 'TEXT' }, { @@ -121,7 +121,7 @@ export class AddonModAssignOfflineProvider { type: 'INTEGER' } ], - primaryKeys: ['assignId', 'userId'] + primaryKeys: ['assignid', 'userid'] } ]; @@ -143,7 +143,8 @@ export class AddonModAssignOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().deleteRecords(this.SUBMISSIONS_TABLE, {assignId, userId}); + return site.getDb().deleteRecords(AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, + {assignid: assignId, userid: userId}); }); } @@ -159,7 +160,8 @@ export class AddonModAssignOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().deleteRecords(this.SUBMISSIONS_GRADES_TABLE, {assignId, userId}); + return site.getDb().deleteRecords(AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, + {assignid: assignId, userid: userId}); }); } @@ -181,7 +183,7 @@ export class AddonModAssignOfflineProvider { // Get assign id. results = results.map((object) => { - return object.assignId; + return object.assignid; }); // Get unique values. @@ -201,12 +203,12 @@ export class AddonModAssignOfflineProvider { */ protected getAllSubmissions(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.SUBMISSIONS_TABLE); + return db.getAllRecords(AddonModAssignOfflineProvider.SUBMISSIONS_TABLE); }).then((submissions) => { // Parse the plugin data. submissions.forEach((submission) => { - submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {}); + submission.plugindata = this.textUtils.parseJSON(submission.plugindata, {}); }); return submissions; @@ -221,13 +223,13 @@ export class AddonModAssignOfflineProvider { */ protected getAllSubmissionsGrade(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.SUBMISSIONS_GRADES_TABLE); + return db.getAllRecords(AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE); }).then((submissions) => { // Parse the plugin data and outcomes. submissions.forEach((submission) => { submission.outcomes = this.textUtils.parseJSON(submission.outcomes, {}); - submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {}); + submission.plugindata = this.textUtils.parseJSON(submission.plugindata, {}); }); return submissions; @@ -243,12 +245,12 @@ export class AddonModAssignOfflineProvider { */ getAssignSubmissions(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecords(this.SUBMISSIONS_TABLE, {assignId}); + return db.getRecords(AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, {assignid: assignId}); }).then((submissions) => { // Parse the plugin data. submissions.forEach((submission) => { - submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {}); + submission.plugindata = this.textUtils.parseJSON(submission.plugindata, {}); }); return submissions; @@ -264,13 +266,13 @@ export class AddonModAssignOfflineProvider { */ getAssignSubmissionsGrade(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecords(this.SUBMISSIONS_GRADES_TABLE, {assignId}); + return db.getRecords(AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, {assignid: assignId}); }).then((submissions) => { // Parse the plugin data and outcomes. submissions.forEach((submission) => { submission.outcomes = this.textUtils.parseJSON(submission.outcomes, {}); - submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {}); + submission.plugindata = this.textUtils.parseJSON(submission.plugindata, {}); }); return submissions; @@ -289,11 +291,11 @@ export class AddonModAssignOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecord(this.SUBMISSIONS_TABLE, {assignId, userId}); + return site.getDb().getRecord(AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, {assignid: assignId, userid: userId}); }).then((submission) => { // Parse the plugin data. - submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {}); + submission.plugindata = this.textUtils.parseJSON(submission.plugindata, {}); return submission; }); @@ -331,12 +333,13 @@ export class AddonModAssignOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecord(this.SUBMISSIONS_GRADES_TABLE, {assignId, userId}); + return site.getDb().getRecord(AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, + {assignid: assignId, userid: userId}); }).then((submission) => { // Parse the plugin data and outcomes. submission.outcomes = this.textUtils.parseJSON(submission.outcomes, {}); - submission.pluginData = this.textUtils.parseJSON(submission.pluginData, {}); + submission.plugindata = this.textUtils.parseJSON(submission.plugindata, {}); return submission; }); @@ -410,20 +413,20 @@ export class AddonModAssignOfflineProvider { const now = this.timeUtils.timestamp(); return { - assignId: assignId, - courseId: courseId, - userId: userId, - onlineTimemodified: timemodified, + assignid: assignId, + courseid: courseId, + userid: userId, + onlinetimemodified: timemodified, timecreated: now, timemodified: now }; }).then((submission) => { // Mark the submission. submission.submitted = submitted ? 1 : 0; - submission.submissionStatement = acceptStatement ? 1 : 0; - submission.pluginData = submission.pluginData ? JSON.stringify(submission.pluginData) : '{}'; + submission.submissionstatement = acceptStatement ? 1 : 0; + submission.plugindata = submission.plugindata ? JSON.stringify(submission.plugindata) : '{}'; - return site.getDb().insertRecord(this.SUBMISSIONS_TABLE, submission); + return site.getDb().insertRecord(AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, submission); }); }); } @@ -448,17 +451,17 @@ export class AddonModAssignOfflineProvider { const now = this.timeUtils.timestamp(), entry = { - assignId: assignId, - courseId: courseId, - pluginData: pluginData ? JSON.stringify(pluginData) : '{}', - userId: userId, + assignid: assignId, + courseid: courseId, + plugindata: pluginData ? JSON.stringify(pluginData) : '{}', + userid: userId, submitted: submitted ? 1 : 0, timecreated: now, timemodified: now, - onlineTimemodified: timemodified + onlinetimemodified: timemodified }; - return site.getDb().insertRecord(this.SUBMISSIONS_TABLE, entry); + return site.getDb().insertRecord(AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, entry); }); } @@ -484,20 +487,20 @@ export class AddonModAssignOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { const now = this.timeUtils.timestamp(), entry = { - assignId: assignId, - userId: userId, - courseId: courseId, + assignid: assignId, + userid: userId, + courseid: courseId, grade: grade, - attemptNumber: attemptNumber, - addAttempt: addAttempt ? 1 : 0, - workflowState: workflowState, - applyToAll: applyToAll ? 1 : 0, + attemptnumber: attemptNumber, + addattempt: addAttempt ? 1 : 0, + workflowstate: workflowState, + applytoall: applyToAll ? 1 : 0, outcomes: outcomes ? JSON.stringify(outcomes) : '{}', - pluginData: pluginData ? JSON.stringify(pluginData) : '{}', + plugindata: pluginData ? JSON.stringify(pluginData) : '{}', timemodified: now }; - return site.getDb().insertRecord(this.SUBMISSIONS_GRADES_TABLE, entry); + return site.getDb().insertRecord(AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, entry); }); } } diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 69aa22a6e..a2576b465 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -213,7 +213,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { return Promise.reject(null); } - courseId = submissions.length > 0 ? submissions[0].courseId : grades[0].courseId; + courseId = submissions.length > 0 ? submissions[0].courseid : grades[0].courseid; return this.assignProvider.getAssignmentById(courseId, assignId, siteId).then((assignData) => { assign = assignData; @@ -264,7 +264,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { * @return {Promise} Promise resolved if success, rejected otherwise. */ protected syncSubmission(assign: any, offlineData: any, warnings: string[], siteId?: string): Promise { - const userId = offlineData.userId, + const userId = offlineData.userid, pluginData = {}; let discardError, submission; @@ -274,7 +274,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { submission = this.assignProvider.getSubmissionObjectFromAttempt(assign, status.lastattempt); - if (submission.timemodified != offlineData.onlineTimemodified) { + if (submission.timemodified != offlineData.onlinetimemodified) { // The submission was modified in Moodle, discard the submission. discardError = this.translate.instant('addon.mod_assign.warningsubmissionmodified'); @@ -300,7 +300,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { return promise.then(() => { if (assign.submissiondrafts && offlineData.submitted) { // The user submitted the assign manually. Submit it for grading. - return this.assignProvider.submitForGradingOnline(assign.id, offlineData.submissionStatement, siteId); + return this.assignProvider.submitForGradingOnline(assign.id, offlineData.submissionstatement, siteId); } }).then(() => { // Submission data sent, update cached data. No need to block the user for this. @@ -355,7 +355,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { protected syncSubmissionGrade(assign: any, offlineData: any, warnings: string[], courseId: number, siteId?: string) : Promise { - const userId = offlineData.userId; + const userId = offlineData.userid; let discardError; return this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, true, siteId).then((status) => { @@ -394,9 +394,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { }); }).then(() => { // Now submit the grade. - return this.assignProvider.submitGradingFormOnline(assign.id, userId, offlineData.grade, offlineData.attemptNumber, - offlineData.addAttempt, offlineData.workflowState, offlineData.applyToAll, offlineData.outcomes, - offlineData.pluginData, siteId).then(() => { + return this.assignProvider.submitGradingFormOnline(assign.id, userId, offlineData.grade, offlineData.attemptnumber, + offlineData.addattempt, offlineData.workflowstate, offlineData.applytoall, offlineData.outcomes, + offlineData.plugindata, siteId).then(() => { // Grades sent, update cached data. No need to block the user for this. this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, true, siteId); diff --git a/src/addon/mod/assign/submission/file/component/file.ts b/src/addon/mod/assign/submission/file/component/file.ts index ed14b8240..a25ac1bbb 100644 --- a/src/addon/mod/assign/submission/file/component/file.ts +++ b/src/addon/mod/assign/submission/file/component/file.ts @@ -47,10 +47,10 @@ export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmiss this.assignOfflineProvider.getSubmission(this.assign.id).catch(() => { // Error getting data, assume there's no offline submission. }).then((offlineData) => { - if (offlineData && offlineData.pluginData && offlineData.pluginData.files_filemanager) { + if (offlineData && offlineData.plugindata && offlineData.plugindata.files_filemanager) { // It has offline data. let promise; - if (offlineData.pluginData.files_filemanager.offline) { + if (offlineData.plugindata.files_filemanager.offline) { promise = this.assignHelper.getStoredSubmissionFiles(this.assign.id, AddonModAssignSubmissionFileHandler.FOLDER_NAME); } else { @@ -58,7 +58,7 @@ export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmiss } return promise.then((offlineFiles) => { - const onlineFiles = offlineData.pluginData.files_filemanager.online || []; + const onlineFiles = offlineData.plugindata.files_filemanager.online || []; offlineFiles = this.fileUploaderProvider.markOfflineFiles(offlineFiles); this.files = onlineFiles.concat(offlineFiles); diff --git a/src/addon/mod/assign/submission/file/providers/handler.ts b/src/addon/mod/assign/submission/file/providers/handler.ts index 316712a1c..1e013db7a 100644 --- a/src/addon/mod/assign/submission/file/providers/handler.ts +++ b/src/addon/mod/assign/submission/file/providers/handler.ts @@ -237,9 +237,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis return this.assignOfflineProvider.getSubmission(assign.id, submission.userid).catch(() => { // No offline data found. }).then((offlineData) => { - if (offlineData && offlineData.pluginData && offlineData.pluginData.files_filemanager) { + if (offlineData && offlineData.plugindata && offlineData.plugindata.files_filemanager) { // Has offline data, return the number of files. - return offlineData.pluginData.files_filemanager.offline + offlineData.pluginData.files_filemanager.online.length; + return offlineData.plugindata.files_filemanager.offline + offlineData.plugindata.files_filemanager.online.length; } // No offline data, return the number of online files. @@ -333,7 +333,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) : void | Promise { - const filesData = offlineData && offlineData.pluginData && offlineData.pluginData.files_filemanager; + const filesData = offlineData && offlineData.plugindata && offlineData.plugindata.files_filemanager; if (filesData) { // Has some data to sync. let files = filesData.online || [], diff --git a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts index 3172d3c73..b2c96f266 100644 --- a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -68,8 +68,8 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS return this.assignOfflineProvider.getSubmission(this.assign.id).catch(() => { // No offline data found. }).then((offlineData) => { - if (offlineData && offlineData.pluginData && offlineData.pluginData.onlinetext_editor) { - return offlineData.pluginData.onlinetext_editor.text; + if (offlineData && offlineData.plugindata && offlineData.plugindata.onlinetext_editor) { + return offlineData.plugindata.onlinetext_editor.text; } // No offline data found, return online text. diff --git a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts index 370b6f28b..4f06dc018 100644 --- a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts +++ b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts @@ -188,8 +188,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign return this.assignOfflineProvider.getSubmission(assign.id, submission.userid).catch(() => { // No offline data found. }).then((data) => { - if (data && data.pluginData && data.pluginData.onlinetext_editor) { - return data.pluginData.onlinetext_editor.text; + if (data && data.plugindata && data.plugindata.onlinetext_editor) { + return data.plugindata.onlinetext_editor.text; } // No offline data found, get text from plugin. diff --git a/src/addon/mod/choice/choice.module.ts b/src/addon/mod/choice/choice.module.ts index 54731cb0a..16f54b7ed 100644 --- a/src/addon/mod/choice/choice.module.ts +++ b/src/addon/mod/choice/choice.module.ts @@ -25,6 +25,7 @@ import { AddonModChoicePrefetchHandler } from './providers/prefetch-handler'; import { AddonModChoiceSyncProvider } from './providers/sync'; import { AddonModChoiceSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModChoiceOfflineProvider } from './providers/offline'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_CHOICE_PROVIDERS: any[] = [ @@ -50,10 +51,26 @@ export class AddonModChoiceModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModChoiceModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModChoicePrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModChoiceLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModChoiceSyncCronHandler) { + cronDelegate: CoreCronDelegate, syncHandler: AddonModChoiceSyncCronHandler, updateManager: CoreUpdateManagerProvider) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); contentLinksDelegate.registerHandler(linkHandler); cronDelegate.register(syncHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_mod_choice_offline_responses', + newName: AddonModChoiceOfflineProvider.CHOICE_TABLE, + fields: [ + { + name: 'responses', + type: 'object' + }, + { + name: 'deleting', + type: 'boolean' + } + ] + }); } } diff --git a/src/addon/mod/choice/providers/offline.ts b/src/addon/mod/choice/providers/offline.ts index ea4e3538e..f17e1d2ed 100644 --- a/src/addon/mod/choice/providers/offline.ts +++ b/src/addon/mod/choice/providers/offline.ts @@ -22,10 +22,10 @@ import { CoreSitesProvider } from '@providers/sites'; export class AddonModChoiceOfflineProvider { // Variables for database. - protected CHOICE_TABLE = 'addon_mod_choice_responses'; + static CHOICE_TABLE = 'addon_mod_choice_responses'; protected tablesSchema = [ { - name: this.CHOICE_TABLE, + name: AddonModChoiceOfflineProvider.CHOICE_TABLE, columns: [ { name: 'choiceid', @@ -76,7 +76,7 @@ export class AddonModChoiceOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().deleteRecords(this.CHOICE_TABLE, {choiceid: choiceId, userid: userId}); + return site.getDb().deleteRecords(AddonModChoiceOfflineProvider.CHOICE_TABLE, {choiceid: choiceId, userid: userId}); }); } @@ -88,7 +88,7 @@ export class AddonModChoiceOfflineProvider { */ getResponses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.CHOICE_TABLE).then((records) => { + return site.getDb().getRecords(AddonModChoiceOfflineProvider.CHOICE_TABLE).then((records) => { records.forEach((record) => { record.responses = JSON.parse(record.responses); }); @@ -127,7 +127,8 @@ export class AddonModChoiceOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecord(this.CHOICE_TABLE, {choiceid: choiceId, userid: userId}).then((record) => { + return site.getDb().getRecord(AddonModChoiceOfflineProvider.CHOICE_TABLE, {choiceid: choiceId, userid: userId}) + .then((record) => { record.responses = JSON.parse(record.responses); return record; @@ -160,7 +161,7 @@ export class AddonModChoiceOfflineProvider { timecreated: new Date().getTime() }; - return site.getDb().insertRecord(this.CHOICE_TABLE, entry); + return site.getDb().insertRecord(AddonModChoiceOfflineProvider.CHOICE_TABLE, entry); }); } } diff --git a/src/addon/mod/data/data.module.ts b/src/addon/mod/data/data.module.ts index 431ac084f..110991b0f 100644 --- a/src/addon/mod/data/data.module.ts +++ b/src/addon/mod/data/data.module.ts @@ -33,6 +33,7 @@ import { AddonModDataOfflineProvider } from './providers/offline'; import { AddonModDataFieldsDelegate } from './providers/fields-delegate'; import { AddonModDataDefaultFieldHandler } from './providers/default-field-handler'; import { AddonModDataFieldModule } from './fields/field.module'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_DATA_PROVIDERS: any[] = [ @@ -66,7 +67,7 @@ export class AddonModDataModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModDataModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModDataPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModDataLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler, + cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler, updateManager: CoreUpdateManagerProvider, approveLinkHandler: AddonModDataApproveLinkHandler, deleteLinkHandler: AddonModDataDeleteLinkHandler, showLinkHandler: AddonModDataShowLinkHandler, editLinkHandler: AddonModDataEditLinkHandler) { moduleDelegate.registerHandler(moduleHandler); @@ -77,5 +78,21 @@ export class AddonModDataModule { contentLinksDelegate.registerHandler(showLinkHandler); contentLinksDelegate.registerHandler(editLinkHandler); cronDelegate.register(syncHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_mod_data_entry', + newName: AddonModDataOfflineProvider.DATA_ENTRY_TABLE, + fields: [ + { + name: 'fields', + type: 'object' + }, + { + name: 'dataAndEntry', + delete: true + } + ] + }); } } diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index c30316b1a..76ca2556d 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -27,10 +27,10 @@ export class AddonModDataOfflineProvider { protected logger; // Variables for database. - protected DATA_ENTRY_TABLE = 'addon_mod_data_entry'; + static DATA_ENTRY_TABLE = 'addon_mod_data_entry'; protected tablesSchema = [ { - name: this.DATA_ENTRY_TABLE, + name: AddonModDataOfflineProvider.DATA_ENTRY_TABLE, columns: [ { name: 'dataid', @@ -102,7 +102,8 @@ export class AddonModDataOfflineProvider { */ deleteEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, action: action}); + return site.getDb().deleteRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, + action: action}); }); } @@ -114,7 +115,9 @@ export class AddonModDataOfflineProvider { */ getAllEntries(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getAllRecords(this.DATA_ENTRY_TABLE); + return site.getDb().getAllRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE); + }).then((entries) => { + return entries.map(this.parseRecord.bind(this)); }); } @@ -127,7 +130,9 @@ export class AddonModDataOfflineProvider { */ getDatabaseEntries(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.DATA_ENTRY_TABLE, {dataid: dataId}); + return site.getDb().getRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId}); + }).then((entries) => { + return entries.map(this.parseRecord.bind(this)); }); } @@ -142,7 +147,10 @@ export class AddonModDataOfflineProvider { */ getEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, action: action}); + return site.getDb().getRecord(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, + action: action}); + }).then((entry) => { + return this.parseRecord(entry); }); } @@ -156,7 +164,9 @@ export class AddonModDataOfflineProvider { */ getEntryActions(dataId: number, entryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId}); + return site.getDb().getRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId}); + }).then((entries) => { + return entries.map(this.parseRecord.bind(this)); }); } @@ -208,6 +218,18 @@ export class AddonModDataOfflineProvider { }); } + /** + * Parse "fields" of an offline record. + * + * @param {any} record Record object + * @return {any} Record object with columns parsed. + */ + protected parseRecord(record: any): any { + record.fields = this.textUtils.parseJSON(record.fields); + + return record; + } + /** * Save an entry data to be sent later. * @@ -233,11 +255,11 @@ export class AddonModDataOfflineProvider { groupid: groupId, action: action, entryid: entryId, - fields: fields, + fields: JSON.stringify(fields || []), timemodified: timemodified }; - return site.getDb().insertRecord(this.DATA_ENTRY_TABLE, entry); + return site.getDb().insertRecord(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, entry); }); } diff --git a/src/addon/mod/feedback/feedback.module.ts b/src/addon/mod/feedback/feedback.module.ts index d80fc9075..8ebb52c4d 100644 --- a/src/addon/mod/feedback/feedback.module.ts +++ b/src/addon/mod/feedback/feedback.module.ts @@ -31,6 +31,7 @@ import { AddonModFeedbackPrefetchHandler } from './providers/prefetch-handler'; import { AddonModFeedbackSyncProvider } from './providers/sync'; import { AddonModFeedbackSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModFeedbackOfflineProvider } from './providers/offline'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_FEEDBACK_PROVIDERS: any[] = [ @@ -63,7 +64,7 @@ export class AddonModFeedbackModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModFeedbackPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModFeedbackLinkHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModFeedbackSyncCronHandler, - analysisLinkHandler: AddonModFeedbackAnalysisLinkHandler, + analysisLinkHandler: AddonModFeedbackAnalysisLinkHandler, updateManager: CoreUpdateManagerProvider, showEntriesLinkHandler: AddonModFeedbackShowEntriesLinkHandler, showNonRespondentsLinkHandler: AddonModFeedbackShowNonRespondentsLinkHandler, completeLinkHandler: AddonModFeedbackCompleteLinkHandler, @@ -77,5 +78,17 @@ export class AddonModFeedbackModule { contentLinksDelegate.registerHandler(completeLinkHandler); contentLinksDelegate.registerHandler(printLinkHandler); cronDelegate.register(syncHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_mod_feedback_responses', + newName: AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, + fields: [ + { + name: 'responses', + type: 'object' + } + ] + }); } } diff --git a/src/addon/mod/feedback/providers/offline.ts b/src/addon/mod/feedback/providers/offline.ts index ffa69a4fd..89c7f5e0f 100644 --- a/src/addon/mod/feedback/providers/offline.ts +++ b/src/addon/mod/feedback/providers/offline.ts @@ -27,10 +27,10 @@ export class AddonModFeedbackOfflineProvider { protected logger; // Variables for database. - protected FEEDBACK_TABLE = 'addon_mod_feedback_answers'; + static FEEDBACK_TABLE = 'addon_mod_feedback_answers'; protected tablesSchema = [ { - name: this.FEEDBACK_TABLE, + name: AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, columns: [ { name: 'feedbackid', @@ -73,7 +73,7 @@ export class AddonModFeedbackOfflineProvider { */ deleteFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.FEEDBACK_TABLE, {feedbackid: feedbackId, page: page}); + return site.getDb().deleteRecords(AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, {feedbackid: feedbackId, page: page}); }); } @@ -85,7 +85,7 @@ export class AddonModFeedbackOfflineProvider { */ getAllFeedbackResponses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getAllRecords(this.FEEDBACK_TABLE).then((entries) => { + return site.getDb().getAllRecords(AddonModFeedbackOfflineProvider.FEEDBACK_TABLE).then((entries) => { return entries.map((entry) => { entry.responses = this.textUtils.parseJSON(entry.responses); }); @@ -102,10 +102,10 @@ export class AddonModFeedbackOfflineProvider { */ getFeedbackResponses(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.FEEDBACK_TABLE, {feedbackid: feedbackId}).then((entries) => { - return entries.map((entry) => { - entry.responses = this.textUtils.parseJSON(entry.responses); - }); + return site.getDb().getRecords(AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, {feedbackid: feedbackId}); + }).then((entries) => { + return entries.map((entry) => { + entry.responses = this.textUtils.parseJSON(entry.responses); }); }); } @@ -120,11 +120,11 @@ export class AddonModFeedbackOfflineProvider { */ getFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.FEEDBACK_TABLE, {feedbackid: feedbackId, page: page}).then((entry) => { - entry.responses = this.textUtils.parseJSON(entry.responses); + return site.getDb().getRecord(AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, {feedbackid: feedbackId, page: page}); + }).then((entry) => { + entry.responses = this.textUtils.parseJSON(entry.responses); - return entry; - }); + return entry; }); } @@ -161,7 +161,7 @@ export class AddonModFeedbackOfflineProvider { timemodified: this.timeUtils.timestamp() }; - return site.getDb().insertRecord(this.FEEDBACK_TABLE, entry); + return site.getDb().insertRecord(AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, entry); }); } } diff --git a/src/addon/mod/folder/providers/pluginfile-handler.ts b/src/addon/mod/folder/providers/pluginfile-handler.ts index b35efe663..943d2b0d4 100644 --- a/src/addon/mod/folder/providers/pluginfile-handler.ts +++ b/src/addon/mod/folder/providers/pluginfile-handler.ts @@ -21,6 +21,7 @@ import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; @Injectable() export class AddonModFolderPluginFileHandler implements CorePluginFileHandler { name = 'AddonModFolderPluginFileHandler'; + component = 'mod_folder'; /** * Return the RegExp to match the revision on pluginfile URLs. diff --git a/src/addon/mod/forum/forum.module.ts b/src/addon/mod/forum/forum.module.ts index 67b9915dc..1aa82db95 100644 --- a/src/addon/mod/forum/forum.module.ts +++ b/src/addon/mod/forum/forum.module.ts @@ -27,6 +27,7 @@ import { AddonModForumSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModForumIndexLinkHandler } from './providers/index-link-handler'; import { AddonModForumDiscussionLinkHandler } from './providers/discussion-link-handler'; import { AddonModForumComponentsModule } from './components/components.module'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_FORUM_PROVIDERS: any[] = [ @@ -54,11 +55,49 @@ export class AddonModForumModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModForumModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModForumPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModForumSyncCronHandler, linksDelegate: CoreContentLinksDelegate, - indexHandler: AddonModForumIndexLinkHandler, discussionHandler: AddonModForumDiscussionLinkHandler) { + indexHandler: AddonModForumIndexLinkHandler, discussionHandler: AddonModForumDiscussionLinkHandler, + updateManager: CoreUpdateManagerProvider) { + moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); cronDelegate.register(syncHandler); linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(discussionHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTablesMigration([ + { + name: 'mma_mod_forum_offline_discussions', + newName: AddonModForumOfflineProvider.DISCUSSIONS_TABLE, + fields: [ + { + name: 'forumAndUser', + delete: true + }, + { + name: 'options', + type: 'object' + } + ] + }, + { + name: 'mma_mod_forum_offline_replies', + newName: AddonModForumOfflineProvider.REPLIES_TABLE, + fields: [ + { + name: 'forumAndUser', + delete: true + }, + { + name: 'discussionAndUser', + delete: true + }, + { + name: 'options', + type: 'object' + } + ] + } + ]); } } diff --git a/src/addon/mod/forum/providers/offline.ts b/src/addon/mod/forum/providers/offline.ts index ac3a44261..4ba0c0e86 100644 --- a/src/addon/mod/forum/providers/offline.ts +++ b/src/addon/mod/forum/providers/offline.ts @@ -24,12 +24,12 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; export class AddonModForumOfflineProvider { // Variables for database. - protected DISCUSSIONS_TABLE = 'addon_mod_forum_discussions'; - protected REPLIES_TABLE = 'addon_mod_forum_replies'; + static DISCUSSIONS_TABLE = 'addon_mod_forum_discussions'; + static REPLIES_TABLE = 'addon_mod_forum_replies'; protected tablesSchema = [ { - name: this.DISCUSSIONS_TABLE, + name: AddonModForumOfflineProvider.DISCUSSIONS_TABLE, columns: [ { name: 'forumid', @@ -71,7 +71,7 @@ export class AddonModForumOfflineProvider { primaryKeys: ['forumid', 'userid', 'timecreated'] }, { - name: this.REPLIES_TABLE, + name: AddonModForumOfflineProvider.REPLIES_TABLE, columns: [ { name: 'postid', @@ -141,7 +141,7 @@ export class AddonModForumOfflineProvider { timecreated: timeCreated, }; - return site.getDb().deleteRecords(this.DISCUSSIONS_TABLE, conditions); + return site.getDb().deleteRecords(AddonModForumOfflineProvider.DISCUSSIONS_TABLE, conditions); }); } @@ -162,7 +162,7 @@ export class AddonModForumOfflineProvider { timecreated: timeCreated, }; - return site.getDb().getRecord(this.DISCUSSIONS_TABLE, conditions).then((record) => { + return site.getDb().getRecord(AddonModForumOfflineProvider.DISCUSSIONS_TABLE, conditions).then((record) => { record.options = this.textUtils.parseJSON(record.options); return record; @@ -178,7 +178,7 @@ export class AddonModForumOfflineProvider { */ getAllNewDiscussions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.DISCUSSIONS_TABLE).then(this.parseRecordOptions.bind(this)); + return site.getDb().getRecords(AddonModForumOfflineProvider.DISCUSSIONS_TABLE).then(this.parseRecordOptions.bind(this)); }); } @@ -214,7 +214,8 @@ export class AddonModForumOfflineProvider { userid: userId || site.getUserId(), }; - return site.getDb().getRecords(this.DISCUSSIONS_TABLE, conditions).then(this.parseRecordOptions.bind(this)); + return site.getDb().getRecords(AddonModForumOfflineProvider.DISCUSSIONS_TABLE, conditions) + .then(this.parseRecordOptions.bind(this)); }); } @@ -248,7 +249,7 @@ export class AddonModForumOfflineProvider { timecreated: timeCreated || new Date().getTime() }; - return site.getDb().insertRecord(this.DISCUSSIONS_TABLE, data); + return site.getDb().insertRecord(AddonModForumOfflineProvider.DISCUSSIONS_TABLE, data); }); } @@ -267,7 +268,7 @@ export class AddonModForumOfflineProvider { userid: userId || site.getUserId(), }; - return site.getDb().deleteRecords(this.REPLIES_TABLE, conditions); + return site.getDb().deleteRecords(AddonModForumOfflineProvider.REPLIES_TABLE, conditions); }); } @@ -279,7 +280,7 @@ export class AddonModForumOfflineProvider { */ getAllReplies(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.REPLIES_TABLE).then(this.parseRecordOptions.bind(this)); + return site.getDb().getRecords(AddonModForumOfflineProvider.REPLIES_TABLE).then(this.parseRecordOptions.bind(this)); }); } @@ -315,7 +316,8 @@ export class AddonModForumOfflineProvider { userid: userId || site.getUserId(), }; - return site.getDb().getRecords(this.REPLIES_TABLE, conditions).then(this.parseRecordOptions.bind(this)); + return site.getDb().getRecords(AddonModForumOfflineProvider.REPLIES_TABLE, conditions) + .then(this.parseRecordOptions.bind(this)); }); } @@ -351,7 +353,8 @@ export class AddonModForumOfflineProvider { userid: userId || site.getUserId(), }; - return site.getDb().getRecords(this.REPLIES_TABLE, conditions).then(this.parseRecordOptions.bind(this)); + return site.getDb().getRecords(AddonModForumOfflineProvider.REPLIES_TABLE, conditions) + .then(this.parseRecordOptions.bind(this)); }); } @@ -386,7 +389,7 @@ export class AddonModForumOfflineProvider { timecreated: new Date().getTime() }; - return site.getDb().insertRecord(this.REPLIES_TABLE, data); + return site.getDb().insertRecord(AddonModForumOfflineProvider.REPLIES_TABLE, data); }); } diff --git a/src/addon/mod/glossary/glossary.module.ts b/src/addon/mod/glossary/glossary.module.ts index 7f606f5d4..148311664 100644 --- a/src/addon/mod/glossary/glossary.module.ts +++ b/src/addon/mod/glossary/glossary.module.ts @@ -27,6 +27,7 @@ import { AddonModGlossarySyncCronHandler } from './providers/sync-cron-handler'; import { AddonModGlossaryIndexLinkHandler } from './providers/index-link-handler'; import { AddonModGlossaryEntryLinkHandler } from './providers/entry-link-handler'; import { AddonModGlossaryComponentsModule } from './components/components.module'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_GLOSSARY_PROVIDERS: any[] = [ @@ -54,11 +55,37 @@ export class AddonModGlossaryModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModGlossaryModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModGlossaryPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModGlossarySyncCronHandler, linksDelegate: CoreContentLinksDelegate, - indexHandler: AddonModGlossaryIndexLinkHandler, discussionHandler: AddonModGlossaryEntryLinkHandler) { + indexHandler: AddonModGlossaryIndexLinkHandler, discussionHandler: AddonModGlossaryEntryLinkHandler, + updateManager: CoreUpdateManagerProvider) { + moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); cronDelegate.register(syncHandler); linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(discussionHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_mod_glossary_add_entry', + newName: AddonModGlossaryOfflineProvider.ENTRIES_TABLE, + fields: [ + { + name: 'glossaryAndConcept', + delete: true + }, + { + name: 'glossaryAndUser', + delete: true + }, + { + name: 'options', + type: 'object' + }, + { + name: 'attachments', + type: 'object' + } + ] + }); } } diff --git a/src/addon/mod/glossary/providers/offline.ts b/src/addon/mod/glossary/providers/offline.ts index d33af7131..0adccdb0b 100644 --- a/src/addon/mod/glossary/providers/offline.ts +++ b/src/addon/mod/glossary/providers/offline.ts @@ -25,11 +25,11 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; export class AddonModGlossaryOfflineProvider { // Variables for database. - protected ENTRIES_TABLE = 'addon_mod_glossary_entrues'; + static ENTRIES_TABLE = 'addon_mod_glossary_entrues'; protected tablesSchema = [ { - name: this.ENTRIES_TABLE, + name: AddonModGlossaryOfflineProvider.ENTRIES_TABLE, columns: [ { name: 'glossaryid', @@ -96,7 +96,7 @@ export class AddonModGlossaryOfflineProvider { timecreated: timeCreated, }; - return site.getDb().deleteRecords(this.ENTRIES_TABLE, conditions); + return site.getDb().deleteRecords(AddonModGlossaryOfflineProvider.ENTRIES_TABLE, conditions); }); } @@ -108,7 +108,7 @@ export class AddonModGlossaryOfflineProvider { */ getAllNewEntries(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.ENTRIES_TABLE).then((records: any[]) => { + return site.getDb().getRecords(AddonModGlossaryOfflineProvider.ENTRIES_TABLE).then((records: any[]) => { return records.map(this.parseRecord.bind(this)); }); }); @@ -131,7 +131,8 @@ export class AddonModGlossaryOfflineProvider { timecreated: timeCreated, }; - return site.getDb().getRecord(this.ENTRIES_TABLE, conditions).then(this.parseRecord.bind(this)); + return site.getDb().getRecord(AddonModGlossaryOfflineProvider.ENTRIES_TABLE, conditions) + .then(this.parseRecord.bind(this)); }); } @@ -150,7 +151,7 @@ export class AddonModGlossaryOfflineProvider { userId: userId || site.getUserId(), }; - return site.getDb().getRecords(this.ENTRIES_TABLE, conditions).then((records: any[]) => { + return site.getDb().getRecords(AddonModGlossaryOfflineProvider.ENTRIES_TABLE, conditions).then((records: any[]) => { return records.map(this.parseRecord.bind(this)); }); }); @@ -172,7 +173,7 @@ export class AddonModGlossaryOfflineProvider { concept: concept, }; - return site.getDb().getRecords(this.ENTRIES_TABLE, conditions).then((entries) => { + return site.getDb().getRecords(AddonModGlossaryOfflineProvider.ENTRIES_TABLE, conditions).then((entries) => { if (!entries.length) { return false; } @@ -229,7 +230,7 @@ export class AddonModGlossaryOfflineProvider { } return discardPromise.then(() => { - return site.getDb().insertRecord(this.ENTRIES_TABLE, entry).then(() => false); + return site.getDb().insertRecord(AddonModGlossaryOfflineProvider.ENTRIES_TABLE, entry).then(() => false); }); }); } diff --git a/src/addon/mod/imscp/providers/pluginfile-handler.ts b/src/addon/mod/imscp/providers/pluginfile-handler.ts index e64e9b44f..f3cd322d6 100644 --- a/src/addon/mod/imscp/providers/pluginfile-handler.ts +++ b/src/addon/mod/imscp/providers/pluginfile-handler.ts @@ -21,6 +21,7 @@ import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; @Injectable() export class AddonModImscpPluginFileHandler implements CorePluginFileHandler { name = 'AddonModImscpPluginFileHandler'; + component = 'mod_imscp'; /** * Return the RegExp to match the revision on pluginfile URLs. diff --git a/src/addon/mod/lesson/components/index/index.ts b/src/addon/mod/lesson/components/index/index.ts index b3b21b9da..59178364b 100644 --- a/src/addon/mod/lesson/components/index/index.ts +++ b/src/addon/mod/lesson/components/index/index.ts @@ -364,7 +364,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo this.navCtrl.push('AddonModLessonPlayerPage', { courseId: this.courseId, lessonId: this.lesson.id, - pageId: this.retakeToReview.pageId, + pageId: this.retakeToReview.pageid, password: this.password, review: true, retake: this.retakeToReview.retake diff --git a/src/addon/mod/lesson/lesson.module.ts b/src/addon/mod/lesson/lesson.module.ts index 42a83b2d0..dc5078ae7 100644 --- a/src/addon/mod/lesson/lesson.module.ts +++ b/src/addon/mod/lesson/lesson.module.ts @@ -28,6 +28,7 @@ import { AddonModLessonSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModLessonIndexLinkHandler } from './providers/index-link-handler'; import { AddonModLessonGradeLinkHandler } from './providers/grade-link-handler'; import { AddonModLessonReportLinkHandler } from './providers/report-link-handler'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_LESSON_PROVIDERS: any[] = [ @@ -57,7 +58,7 @@ export class AddonModLessonModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModLessonPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModLessonSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModLessonIndexLinkHandler, gradeHandler: AddonModLessonGradeLinkHandler, - reportHandler: AddonModLessonReportLinkHandler) { + reportHandler: AddonModLessonReportLinkHandler, updateManager: CoreUpdateManagerProvider) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -65,5 +66,71 @@ export class AddonModLessonModule { linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(gradeHandler); linksDelegate.registerHandler(reportHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTablesMigration([ + { + name: 'mma_mod_lesson_password', + newName: AddonModLessonProvider.PASSWORD_TABLE, + fields: [ + { + name: 'id', + newName: 'lessonid' + } + ] + }, + { + name: 'mma_mod_lesson_retakes', + newName: AddonModLessonOfflineProvider.RETAKES_TABLE, + fields: [ + { + name: 'finished', + type: 'boolean' + }, + { + name: 'outoftime', + type: 'boolean' + } + ] + }, + { + name: 'mma_mod_lesson_page_attempts', + newName: AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, + fields: [ + { + name: 'lessonAndPage', + delete: true + }, + { + name: 'lessonAndRetake', + delete: true + }, + { + name: 'lessonAndRetakeAndType', + delete: true + }, + { + name: 'lessonAndRetakeAndPage', + delete: true + }, + { + name: 'data', + type: 'object' + }, + { + name: 'correct', + type: 'boolean' + }, + { + name: 'userAnswer', + type: 'object' + } + ] + }, + { + name: 'mma_mod_lesson_retakes_finished_sync', + newName: AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE + } + ]); } } diff --git a/src/addon/mod/lesson/providers/lesson-offline.ts b/src/addon/mod/lesson/providers/lesson-offline.ts index d1d80cf14..cf037fadb 100644 --- a/src/addon/mod/lesson/providers/lesson-offline.ts +++ b/src/addon/mod/lesson/providers/lesson-offline.ts @@ -29,11 +29,11 @@ export class AddonModLessonOfflineProvider { protected logger; // Variables for database. We use lowercase in the names to match the WS responses. - protected RETAKES_TABLE = 'addon_mod_lesson_retakes'; - protected PAGE_ATTEMPTS_TABLE = 'addon_mod_lesson_page_attempts'; + static RETAKES_TABLE = 'addon_mod_lesson_retakes'; + static PAGE_ATTEMPTS_TABLE = 'addon_mod_lesson_page_attempts'; protected tablesSchema = [ { - name: this.RETAKES_TABLE, + name: AddonModLessonOfflineProvider.RETAKES_TABLE, columns: [ { name: 'lessonid', @@ -68,7 +68,7 @@ export class AddonModLessonOfflineProvider { ] }, { - name: this.PAGE_ATTEMPTS_TABLE, + name: AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, columns: [ { name: 'lessonid', @@ -142,7 +142,7 @@ export class AddonModLessonOfflineProvider { */ deleteAttempt(lessonId: number, retake: number, pageId: number, timemodified: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.PAGE_ATTEMPTS_TABLE, { + return site.getDb().deleteRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, { lessonid: lessonId, retake: retake, pageid: pageId, @@ -160,7 +160,7 @@ export class AddonModLessonOfflineProvider { */ deleteRetake(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.RETAKES_TABLE, {lessonid: lessonId}); + return site.getDb().deleteRecords(AddonModLessonOfflineProvider.RETAKES_TABLE, {lessonid: lessonId}); }); } @@ -175,7 +175,8 @@ export class AddonModLessonOfflineProvider { */ deleteRetakeAttemptsForPage(lessonId: number, retake: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, pageid: pageId}); + return site.getDb().deleteRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, + retake: retake, pageid: pageId}); }); } @@ -200,7 +201,7 @@ export class AddonModLessonOfflineProvider { entry.outoftime = outOfTime ? 1 : 0; entry.timemodified = this.timeUtils.timestamp(); - return site.getDb().insertRecord(this.RETAKES_TABLE, entry); + return site.getDb().insertRecord(AddonModLessonOfflineProvider.RETAKES_TABLE, entry); }); }); } @@ -213,7 +214,7 @@ export class AddonModLessonOfflineProvider { */ getAllAttempts(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.PAGE_ATTEMPTS_TABLE); + return db.getAllRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE); }).then((attempts) => { return this.parsePageAttempts(attempts); }); @@ -256,7 +257,7 @@ export class AddonModLessonOfflineProvider { */ getAllRetakes(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.RETAKES_TABLE); + return db.getAllRecords(AddonModLessonOfflineProvider.RETAKES_TABLE); }); } @@ -297,7 +298,7 @@ export class AddonModLessonOfflineProvider { */ getLessonAttempts(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId}); + return site.getDb().getRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId}); }).then((attempts) => { return this.parsePageAttempts(attempts); }); @@ -361,7 +362,7 @@ export class AddonModLessonOfflineProvider { */ getRetake(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.RETAKES_TABLE, {lessonid: lessonId}); + return site.getDb().getRecord(AddonModLessonOfflineProvider.RETAKES_TABLE, {lessonid: lessonId}); }); } @@ -375,7 +376,7 @@ export class AddonModLessonOfflineProvider { */ getRetakeAttempts(lessonId: number, retake: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake}); + return site.getDb().getRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake}); }).then((attempts) => { return this.parsePageAttempts(attempts); }); @@ -392,7 +393,8 @@ export class AddonModLessonOfflineProvider { */ getRetakeAttemptsForPage(lessonId: number, retake: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, pageid: pageId}); + return site.getDb().getRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, + pageid: pageId}); }).then((attempts) => { return this.parsePageAttempts(attempts); }); @@ -409,7 +411,8 @@ export class AddonModLessonOfflineProvider { */ getRetakeAttemptsForType(lessonId: number, retake: number, type: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, type: type}); + return site.getDb().getRecords(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, + type: type}); }).then((attempts) => { return this.parsePageAttempts(attempts); }); @@ -563,7 +566,7 @@ export class AddonModLessonOfflineProvider { useranswer: userAnswer ? JSON.stringify(userAnswer) : null, }; - return site.getDb().insertRecord(this.PAGE_ATTEMPTS_TABLE, entry); + return site.getDb().insertRecord(AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, entry); }).then(() => { if (page.type == AddonModLessonProvider.TYPE_QUESTION) { // It's a question page, set it as last question page attempted. @@ -591,7 +594,7 @@ export class AddonModLessonOfflineProvider { entry.lastquestionpage = lastPage; entry.timemodified = this.timeUtils.timestamp(); - return site.getDb().insertRecord(this.RETAKES_TABLE, entry); + return site.getDb().insertRecord(AddonModLessonOfflineProvider.RETAKES_TABLE, entry); }); }); } diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index e2cd2841c..87ddd520b 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -58,12 +58,12 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { protected componentTranslate: string; // Variables for database. - protected RETAKES_FINISHED_TABLE = 'addon_mod_lesson_retakes_finished_sync'; + static RETAKES_FINISHED_TABLE = 'addon_mod_lesson_retakes_finished_sync'; protected tablesSchema = { - name: this.RETAKES_FINISHED_TABLE, + name: AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, columns: [ { - name: 'lessonId', + name: 'lessonid', type: 'INTEGER', primaryKey: true }, @@ -72,7 +72,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { type: 'INTEGER' }, { - name: 'pageId', + name: 'pageid', type: 'INTEGER' }, { @@ -105,7 +105,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { */ deleteRetakeFinishedInSync(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.RETAKES_FINISHED_TABLE, {lessonId}); + return site.getDb().deleteRecords(AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, {lessonid: lessonId}); }).catch(() => { // Ignore errors, maybe there is none. }); @@ -120,7 +120,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { */ getRetakeFinishedInSync(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.RETAKES_FINISHED_TABLE, {lessonId}); + return site.getDb().getRecord(AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, {lessonid: lessonId}); }).catch(() => { // Ignore errors, return undefined. }); @@ -164,10 +164,10 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { */ setRetakeFinishedInSync(lessonId: number, retake: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().insertRecord(this.RETAKES_FINISHED_TABLE, { - lessonId: lessonId, + return site.getDb().insertRecord(AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, { + lessonid: lessonId, retake: Number(retake), - pageId: Number(pageId), + pageid: Number(pageId), timefinished: this.timeUtils.timestamp() }); }); diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index 529b332e4..64eac2a9f 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -148,12 +148,12 @@ export class AddonModLessonProvider { static LESSON_PAGE_ENDOFCLUSTER = 31; // Variables for database. - protected PASSWORD_TABLE = 'addon_mod_lesson_password'; + static PASSWORD_TABLE = 'addon_mod_lesson_password'; protected tablesSchema = { - name: this.PASSWORD_TABLE, + name: AddonModLessonProvider.PASSWORD_TABLE, columns: [ { - name: 'lessonId', + name: 'lessonid', type: 'INTEGER', primaryKey: true }, @@ -2072,7 +2072,7 @@ export class AddonModLessonProvider { */ getStoredPassword(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.PASSWORD_TABLE, {lessonId}).then((entry) => { + return site.getDb().getRecord(AddonModLessonProvider.PASSWORD_TABLE, {lessonid: lessonId}).then((entry) => { return entry.password; }); }); @@ -3169,7 +3169,7 @@ export class AddonModLessonProvider { */ removeStoredPassword(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.PASSWORD_TABLE, {lessonId}); + return site.getDb().deleteRecords(AddonModLessonProvider.PASSWORD_TABLE, {lessonid: lessonId}); }); } @@ -3184,12 +3184,12 @@ export class AddonModLessonProvider { storePassword(lessonId: number, password: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const entry = { - lessonId: lessonId, + lessonid: lessonId, password: password, timemodified: Date.now() }; - return site.getDb().insertRecord(this.PASSWORD_TABLE, entry); + return site.getDb().insertRecord(AddonModLessonProvider.PASSWORD_TABLE, entry); }); } diff --git a/src/addon/mod/page/providers/pluginfile-handler.ts b/src/addon/mod/page/providers/pluginfile-handler.ts index 63719c389..4fa49fcdc 100644 --- a/src/addon/mod/page/providers/pluginfile-handler.ts +++ b/src/addon/mod/page/providers/pluginfile-handler.ts @@ -21,6 +21,7 @@ import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; @Injectable() export class AddonModPagePluginFileHandler implements CorePluginFileHandler { name = 'AddonModPagePluginFileHandler'; + component = 'mod_page'; /** * Return the RegExp to match the revision on pluginfile URLs. diff --git a/src/addon/mod/quiz/accessrules/password/password.module.ts b/src/addon/mod/quiz/accessrules/password/password.module.ts index bfb5b6d95..7c33fa616 100644 --- a/src/addon/mod/quiz/accessrules/password/password.module.ts +++ b/src/addon/mod/quiz/accessrules/password/password.module.ts @@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { AddonModQuizAccessPasswordHandler } from './providers/handler'; import { AddonModQuizAccessPasswordComponent } from './component/password'; import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-delegate'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; @NgModule({ declarations: [ @@ -42,7 +43,14 @@ import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-del ] }) export class AddonModQuizAccessPasswordModule { - constructor(accessRuleDelegate: AddonModQuizAccessRuleDelegate, handler: AddonModQuizAccessPasswordHandler) { + constructor(accessRuleDelegate: AddonModQuizAccessRuleDelegate, handler: AddonModQuizAccessPasswordHandler, + updateManager: CoreUpdateManagerProvider) { accessRuleDelegate.registerHandler(handler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mod_quiz_access_password', + newName: AddonModQuizAccessPasswordHandler.PASSWORD_TABLE + }); } } diff --git a/src/addon/mod/quiz/accessrules/password/providers/handler.ts b/src/addon/mod/quiz/accessrules/password/providers/handler.ts index 7280b2e16..eea3286b2 100644 --- a/src/addon/mod/quiz/accessrules/password/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/password/providers/handler.ts @@ -23,13 +23,10 @@ import { AddonModQuizAccessPasswordComponent } from '../component/password'; */ @Injectable() export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRuleHandler { - name = 'AddonModQuizAccessPassword'; - ruleName = 'quizaccess_password'; - // Variables for database. - protected PASSWORD_TABLE = 'mod_quiz_access_password'; + static PASSWORD_TABLE = 'addon_mod_quiz_access_password'; protected tableSchema = { - name: this.PASSWORD_TABLE, + name: AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, columns: [ { name: 'id', @@ -47,6 +44,9 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule ] }; + name = 'AddonModQuizAccessPassword'; + ruleName = 'quizaccess_password'; + constructor(private sitesProvider: CoreSitesProvider) { this.sitesProvider.createTableFromSchema(this.tableSchema); } @@ -82,7 +82,7 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule protected getPasswordEntry(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.PASSWORD_TABLE, {id: quizId}); + return site.getDb().getRecord(AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, {id: quizId}); }); } @@ -174,7 +174,7 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule protected removePassword(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.PASSWORD_TABLE, {id: quizId}); + return site.getDb().deleteRecords(AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, {id: quizId}); }); } @@ -195,7 +195,7 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule timemodified: Date.now() }; - return site.getDb().insertRecord(this.PASSWORD_TABLE, entry); + return site.getDb().insertRecord(AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, entry); }); } } diff --git a/src/addon/mod/quiz/providers/quiz-offline.ts b/src/addon/mod/quiz/providers/quiz-offline.ts index 34507bd55..76f7a2631 100644 --- a/src/addon/mod/quiz/providers/quiz-offline.ts +++ b/src/addon/mod/quiz/providers/quiz-offline.ts @@ -32,10 +32,10 @@ export class AddonModQuizOfflineProvider { protected logger; // Variables for database. - protected ATTEMPTS_TABLE = 'addon_mod_quiz_attempts'; + static ATTEMPTS_TABLE = 'addon_mod_quiz_attempts'; protected tablesSchema = [ { - name: this.ATTEMPTS_TABLE, + name: AddonModQuizOfflineProvider.ATTEMPTS_TABLE, columns: [ { name: 'id', // Attempt ID. @@ -141,7 +141,7 @@ export class AddonModQuizOfflineProvider { */ getAllAttempts(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.ATTEMPTS_TABLE); + return db.getAllRecords(AddonModQuizOfflineProvider.ATTEMPTS_TABLE); }); } @@ -165,7 +165,7 @@ export class AddonModQuizOfflineProvider { */ getAttemptById(attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecord(this.ATTEMPTS_TABLE, {id: attemptId}); + return db.getRecord(AddonModQuizOfflineProvider.ATTEMPTS_TABLE, {id: attemptId}); }); } @@ -181,7 +181,7 @@ export class AddonModQuizOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecords(this.ATTEMPTS_TABLE, {quizid: quizId, userid: userId}); + return site.getDb().getRecords(AddonModQuizOfflineProvider.ATTEMPTS_TABLE, {quizid: quizId, userid: userId}); }); } @@ -251,7 +251,7 @@ export class AddonModQuizOfflineProvider { entry.timemodified = now; entry.finished = finish ? 1 : 0; - return db.insertRecord(this.ATTEMPTS_TABLE, entry); + return db.insertRecord(AddonModQuizOfflineProvider.ATTEMPTS_TABLE, entry); }).then(() => { // Attempt has been saved, now we need to save the answers. return this.saveAnswers(quiz, attempt, questions, data, now, siteId); @@ -276,7 +276,7 @@ export class AddonModQuizOfflineProvider { // Remove the attempt. promises.push(this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.deleteRecords(this.ATTEMPTS_TABLE, {id: attemptId}); + return db.deleteRecords(AddonModQuizOfflineProvider.ATTEMPTS_TABLE, {id: attemptId}); })); return Promise.all(promises); @@ -379,7 +379,7 @@ export class AddonModQuizOfflineProvider { */ setAttemptCurrentPage(attemptId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.updateRecords(this.ATTEMPTS_TABLE, {currentpage: page}, {id: attemptId}); + return db.updateRecords(AddonModQuizOfflineProvider.ATTEMPTS_TABLE, {currentpage: page}, {id: attemptId}); }); } } diff --git a/src/addon/mod/quiz/quiz.module.ts b/src/addon/mod/quiz/quiz.module.ts index 0f5e2e8bc..e76c4776d 100644 --- a/src/addon/mod/quiz/quiz.module.ts +++ b/src/addon/mod/quiz/quiz.module.ts @@ -29,6 +29,7 @@ import { AddonModQuizIndexLinkHandler } from './providers/index-link-handler'; import { AddonModQuizGradeLinkHandler } from './providers/grade-link-handler'; import { AddonModQuizReviewLinkHandler } from './providers/review-link-handler'; import { AddonModQuizComponentsModule } from './components/components.module'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // Access rules. import { AddonModQuizAccessDelayBetweenAttemptsModule } from './accessrules/delaybetweenattempts/delaybetweenattempts.module'; @@ -79,7 +80,7 @@ export class AddonModQuizModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModQuizPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModQuizSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModQuizIndexLinkHandler, gradeHandler: AddonModQuizGradeLinkHandler, - reviewHandler: AddonModQuizReviewLinkHandler) { + reviewHandler: AddonModQuizReviewLinkHandler, updateManager: CoreUpdateManagerProvider) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -87,5 +88,21 @@ export class AddonModQuizModule { linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(gradeHandler); linksDelegate.registerHandler(reviewHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mod_quiz_attempts', + newName: AddonModQuizOfflineProvider.ATTEMPTS_TABLE, + fields: [ + { + name: 'quizAndUser', + delete: true + }, + { + name: 'finished', + type: 'boolean' + } + ] + }); } } diff --git a/src/addon/mod/resource/providers/pluginfile-handler.ts b/src/addon/mod/resource/providers/pluginfile-handler.ts index daa3da78b..ecd7154f1 100644 --- a/src/addon/mod/resource/providers/pluginfile-handler.ts +++ b/src/addon/mod/resource/providers/pluginfile-handler.ts @@ -21,6 +21,7 @@ import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; @Injectable() export class AddonModResourcePluginFileHandler implements CorePluginFileHandler { name = 'AddonModResourcePluginFileHandler'; + component = 'mod_resource'; /** * Return the RegExp to match the revision on pluginfile URLs. diff --git a/src/addon/mod/scorm/providers/pluginfile-handler.ts b/src/addon/mod/scorm/providers/pluginfile-handler.ts index 6910b67b4..58e16d725 100644 --- a/src/addon/mod/scorm/providers/pluginfile-handler.ts +++ b/src/addon/mod/scorm/providers/pluginfile-handler.ts @@ -21,6 +21,7 @@ import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; @Injectable() export class AddonModScormPluginFileHandler implements CorePluginFileHandler { name = 'AddonModScormPluginFileHandler'; + component = 'mod_scorm'; /** * Return the RegExp to match the revision on pluginfile URLs. @@ -32,7 +33,7 @@ export class AddonModScormPluginFileHandler implements CorePluginFileHandler { // Check filearea. if (args[2] == 'content') { // Component + Filearea + Revision - return new RegExp('/mod_resource/content/([0-9]+)/'); + return new RegExp('/mod_scorm/content/([0-9]+)/'); } } diff --git a/src/addon/mod/scorm/providers/scorm-offline.ts b/src/addon/mod/scorm/providers/scorm-offline.ts index 98842781b..910628d46 100644 --- a/src/addon/mod/scorm/providers/scorm-offline.ts +++ b/src/addon/mod/scorm/providers/scorm-offline.ts @@ -32,14 +32,14 @@ export class AddonModScormOfflineProvider { protected logger; // Variables for database. - protected ATTEMPTS_TABLE = 'addon_mod_scorm_offline_attempts'; - protected TRACKS_TABLE = 'addon_mod_scorm_offline_scos_tracks'; + static ATTEMPTS_TABLE = 'addon_mod_scorm_offline_attempts'; + static TRACKS_TABLE = 'addon_mod_scorm_offline_scos_tracks'; protected tablesSchema = [ { - name: this.ATTEMPTS_TABLE, + name: AddonModScormOfflineProvider.ATTEMPTS_TABLE, columns: [ { - name: 'scormId', + name: 'scormid', type: 'INTEGER', notNull: true }, @@ -49,12 +49,12 @@ export class AddonModScormOfflineProvider { notNull: true }, { - name: 'userId', + name: 'userid', type: 'INTEGER', notNull: true }, { - name: 'courseId', + name: 'courseid', type: 'INTEGER' }, { @@ -70,13 +70,13 @@ export class AddonModScormOfflineProvider { type: 'TEXT' }, ], - primaryKeys: ['scormId', 'userId', 'attempt'] + primaryKeys: ['scormid', 'userid', 'attempt'] }, { - name: this.TRACKS_TABLE, + name: AddonModScormOfflineProvider.TRACKS_TABLE, columns: [ { - name: 'scormId', + name: 'scormid', type: 'INTEGER', notNull: true }, @@ -86,12 +86,12 @@ export class AddonModScormOfflineProvider { notNull: true }, { - name: 'userId', + name: 'userid', type: 'INTEGER', notNull: true }, { - name: 'scoId', + name: 'scoid', type: 'INTEGER', notNull: true }, @@ -113,7 +113,7 @@ export class AddonModScormOfflineProvider { type: 'INTEGER' }, ], - primaryKeys: ['scormId', 'userId', 'attempt', 'scoId', 'element'] + primaryKeys: ['scormid', 'userid', 'attempt', 'scoid', 'element'] } ]; @@ -145,7 +145,9 @@ export class AddonModScormOfflineProvider { this.logger.debug('Change attempt number from ' + attempt + ' to ' + newAttempt + ' in SCORM ' + scormId); // Update the attempt number. - const db = site.getDb(); + const db = site.getDb(), + currentAttemptConditions = {scormid: scormId, userid: userId, attempt: attempt}, + newAttemptConditions = {scormid: scormId, userid: userId, attempt: newAttempt}; let newData: any = { attempt: newAttempt, timemodified: this.timeUtils.timestamp() @@ -154,7 +156,7 @@ export class AddonModScormOfflineProvider { // Block the SCORM so it can't be synced. this.syncProvider.blockOperation(AddonModScormProvider.COMPONENT, scormId, 'changeAttemptNumber', site.id); - return db.updateRecords(this.ATTEMPTS_TABLE, newData, {scormId, userId, attempt}).then(() => { + return db.updateRecords(AddonModScormOfflineProvider.ATTEMPTS_TABLE, newData, currentAttemptConditions).then(() => { // Now update the attempt number of all the tracks and mark them as not synced. newData = { @@ -162,9 +164,11 @@ export class AddonModScormOfflineProvider { synced: 0 }; - return db.updateRecords(this.TRACKS_TABLE, newData, {scormId, userId, attempt}).catch((error) => { + return db.updateRecords(AddonModScormOfflineProvider.TRACKS_TABLE, newData, currentAttemptConditions) + .catch((error) => { // Failed to update the tracks, restore the old attempt number. - return db.updateRecords(this.ATTEMPTS_TABLE, { attempt }, {scormId, userId, attempt: newAttempt}).then(() => { + return db.updateRecords(AddonModScormOfflineProvider.ATTEMPTS_TABLE, { attempt: attempt }, + newAttemptConditions).then(() => { return Promise.reject(error); }); }); @@ -199,10 +203,10 @@ export class AddonModScormOfflineProvider { // Create attempt in DB. const db = site.getDb(), entry: any = { - scormId: scorm.id, - userId: userId, + scormid: scorm.id, + userid: userId, attempt: attempt, - courseId: scorm.course, + courseid: scorm.course, timecreated: this.timeUtils.timestamp(), timemodified: this.timeUtils.timestamp(), snapshot: null @@ -214,7 +218,7 @@ export class AddonModScormOfflineProvider { entry.snapshot = JSON.stringify(this.removeDefaultData(snapshot)); } - return db.insertRecord(this.ATTEMPTS_TABLE, entry).then(() => { + return db.insertRecord(AddonModScormOfflineProvider.ATTEMPTS_TABLE, entry).then(() => { // Store all the data in userData. const promises = []; @@ -256,10 +260,12 @@ export class AddonModScormOfflineProvider { db = site.getDb(); // Delete the attempt. - promises.push(db.deleteRecords(this.ATTEMPTS_TABLE, {scormId, userId, attempt})); + promises.push(db.deleteRecords(AddonModScormOfflineProvider.ATTEMPTS_TABLE, {scormid: scormId, userid: userId, + attempt: attempt})); // Delete all the tracks. - promises.push(db.deleteRecords(this.TRACKS_TABLE, {scormId, userId, attempt})); + promises.push(db.deleteRecords(AddonModScormOfflineProvider.TRACKS_TABLE, {scormid: scormId, userid: userId, + attempt: attempt})); return Promise.all(promises); }); @@ -329,7 +335,7 @@ export class AddonModScormOfflineProvider { */ getAllAttempts(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.ATTEMPTS_TABLE); + return db.getAllRecords(AddonModScormOfflineProvider.ATTEMPTS_TABLE); }).then((attempts) => { attempts.forEach((attempt) => { attempt.snapshot = this.textUtils.parseJSON(attempt.snapshot); @@ -352,11 +358,12 @@ export class AddonModScormOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecord(this.ATTEMPTS_TABLE, {scormId, userId, attempt}).then((entry) => { - entry.snapshot = this.textUtils.parseJSON(entry.snapshot); + return site.getDb().getRecord(AddonModScormOfflineProvider.ATTEMPTS_TABLE, {scormid: scormId, userid: userId, + attempt: attempt}); + }).then((entry) => { + entry.snapshot = this.textUtils.parseJSON(entry.snapshot); - return entry; - }); + return entry; }); } @@ -389,7 +396,7 @@ export class AddonModScormOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecords(this.ATTEMPTS_TABLE, {scormId, userId}); + return site.getDb().getRecords(AddonModScormOfflineProvider.ATTEMPTS_TABLE, {scormid: scormId, userid: userId}); }).then((attempts) => { attempts.forEach((attempt) => { attempt.snapshot = this.textUtils.parseJSON(attempt.snapshot); @@ -454,8 +461,8 @@ export class AddonModScormOfflineProvider { userId = userId || site.getUserId(); const conditions: any = { - scormId: scormId, - userId: userId, + scormid: scormId, + userid: userId, attempt: attempt }; @@ -465,7 +472,7 @@ export class AddonModScormOfflineProvider { conditions.synced = 1; } - return site.getDb().getRecords(this.TRACKS_TABLE, conditions); + return site.getDb().getRecords(AddonModScormOfflineProvider.TRACKS_TABLE, conditions); }).then((tracks) => { tracks.forEach((track) => { track.value = this.textUtils.parseJSON(track.value); @@ -513,7 +520,7 @@ export class AddonModScormOfflineProvider { // Gather user data retrieved from DB, grouping it by scoid. entries.forEach((entry) => { - const scoId = entry.scoId; + const scoId = entry.scoid; if (!response[scoId]) { // Initialize SCO. @@ -690,9 +697,9 @@ export class AddonModScormOfflineProvider { value: any, synchronous?: boolean): boolean | Promise { const entry = { - userId: userId, - scormId: scormId, - scoId: scoId, + userid: userId, + scormid: scormId, + scoid: scoId, attempt: attempt, element: element, value: typeof value == 'undefined' ? null : JSON.stringify(value), @@ -702,11 +709,11 @@ export class AddonModScormOfflineProvider { if (synchronous) { // The insert operation is always asynchronous, always return true. - db.insertRecord(this.TRACKS_TABLE, entry); + db.insertRecord(AddonModScormOfflineProvider.TRACKS_TABLE, entry); return true; } else { - return db.insertRecord(this.TRACKS_TABLE, entry); + return db.insertRecord(AddonModScormOfflineProvider.TRACKS_TABLE, entry); } } @@ -788,11 +795,11 @@ export class AddonModScormOfflineProvider { this.logger.debug('Mark SCO ' + scoId + ' as synced for attempt ' + attempt + ' in SCORM ' + scormId); - return site.getDb().updateRecords(this.TRACKS_TABLE, {synced: 1}, { - scormId: scormId, - userId: userId, + return site.getDb().updateRecords(AddonModScormOfflineProvider.TRACKS_TABLE, {synced: 1}, { + scormid: scormId, + userid: userId, attempt: attempt, - scoId: scoId, + scoid: scoId, synced: 0 }); }); @@ -914,7 +921,8 @@ export class AddonModScormOfflineProvider { snapshot: JSON.stringify(this.removeDefaultData(userData)) }; - return site.getDb().updateRecords(this.ATTEMPTS_TABLE, newData, { scormId, userId, attempt }); + return site.getDb().updateRecords(AddonModScormOfflineProvider.ATTEMPTS_TABLE, newData, { scormid: scormId, + userid: userId, attempt: attempt }); }); } } diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index f7fc6428c..832508473 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -379,13 +379,13 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { // We need to add the synced data to the snapshot. return this.scormOfflineProvider.getScormStoredData(scormId, attempt, false, true, siteId).then((synced) => { synced.forEach((entry) => { - if (!data[entry.scoId]) { - data[entry.scoId] = { - scoid: entry.scoId, + if (!data[entry.scoid]) { + data[entry.scoid] = { + scoid: entry.scoid, userdata: {} }; } - data[entry.scoId].userdata[entry.element] = entry.value; + data[entry.scoid].userdata[entry.element] = entry.value; }); return this.scormOfflineProvider.setAttemptSnapshot(scormId, attempt, data, siteId); @@ -461,12 +461,12 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { // Get the IDs of all the SCORMs that have something to be synced. attempts.forEach((attempt) => { - if (ids.indexOf(attempt.scormId) == -1) { - ids.push(attempt.scormId); + if (ids.indexOf(attempt.scormid) == -1) { + ids.push(attempt.scormid); scorms.push({ - id: attempt.scormId, - courseId: attempt.courseId + id: attempt.scormid, + courseId: attempt.courseid }); } }); @@ -517,11 +517,11 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { // Get data to send (only elements with dots like cmi.core.exit, in Mobile we store more data to make offline work). entries.forEach((entry) => { if (entry.element.indexOf('.') > -1) { - if (!scos[entry.scoId]) { - scos[entry.scoId] = []; + if (!scos[entry.scoid]) { + scos[entry.scoid] = []; } - scos[entry.scoId].push({ + scos[entry.scoid].push({ element: entry.element, value: entry.value }); diff --git a/src/addon/mod/scorm/scorm.module.ts b/src/addon/mod/scorm/scorm.module.ts index 11c0c6e12..ba1fda848 100644 --- a/src/addon/mod/scorm/scorm.module.ts +++ b/src/addon/mod/scorm/scorm.module.ts @@ -27,6 +27,7 @@ import { AddonModScormIndexLinkHandler } from './providers/index-link-handler'; import { AddonModScormGradeLinkHandler } from './providers/grade-link-handler'; import { AddonModScormSyncProvider } from './providers/scorm-sync'; import { AddonModScormComponentsModule } from './components/components.module'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_SCORM_PROVIDERS: any[] = [ @@ -54,12 +55,49 @@ export class AddonModScormModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModScormModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModScormPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModScormSyncCronHandler, linksDelegate: CoreContentLinksDelegate, - indexHandler: AddonModScormIndexLinkHandler, gradeHandler: AddonModScormGradeLinkHandler) { + indexHandler: AddonModScormIndexLinkHandler, gradeHandler: AddonModScormGradeLinkHandler, + updateManager: CoreUpdateManagerProvider) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); cronDelegate.register(syncHandler); linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(gradeHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTablesMigration([ + { + name: 'mod_scorm_offline_attempts', + newName: AddonModScormOfflineProvider.ATTEMPTS_TABLE, + fields: [ + { + name: 'snapshot', + type: 'object' + }, + { + name: 'scormAndUser', + delete: true + } + ] + }, + { + name: 'mod_scorm_offline_scos_tracks', + newName: AddonModScormOfflineProvider.TRACKS_TABLE, + fields: [ + { + name: 'value', + type: 'object' + }, + { + name: 'scormUserAttempt', + delete: true + }, + { + name: 'scormUserAttemptSynced', + delete: true + } + ] + } + ]); } } diff --git a/src/addon/mod/survey/providers/offline.ts b/src/addon/mod/survey/providers/offline.ts index 07b2065fb..2a9faafa1 100644 --- a/src/addon/mod/survey/providers/offline.ts +++ b/src/addon/mod/survey/providers/offline.ts @@ -26,10 +26,10 @@ export class AddonModSurveyOfflineProvider { protected logger; // Variables for database. - protected SURVEY_TABLE = 'addon_mod_survey_answers'; + static SURVEY_TABLE = 'addon_mod_survey_answers'; protected tablesSchema = [ { - name: this.SURVEY_TABLE, + name: AddonModSurveyOfflineProvider.SURVEY_TABLE, columns: [ { name: 'surveyid', @@ -77,7 +77,7 @@ export class AddonModSurveyOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().deleteRecords(this.SURVEY_TABLE, {surveyid: surveyId, userid: userId}); + return site.getDb().deleteRecords(AddonModSurveyOfflineProvider.SURVEY_TABLE, {surveyid: surveyId, userid: userId}); }); } @@ -89,7 +89,7 @@ export class AddonModSurveyOfflineProvider { */ getAllData(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getAllRecords(this.SURVEY_TABLE).then((entries) => { + return site.getDb().getAllRecords(AddonModSurveyOfflineProvider.SURVEY_TABLE).then((entries) => { return entries.map((entry) => { entry.answers = this.textUtils.parseJSON(entry.answers); }); @@ -125,11 +125,11 @@ export class AddonModSurveyOfflineProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.getDb().getRecord(this.SURVEY_TABLE, {surveyid: surveyId, userid: userId}).then((entry) => { - entry.answers = this.textUtils.parseJSON(entry.answers); + return site.getDb().getRecord(AddonModSurveyOfflineProvider.SURVEY_TABLE, {surveyid: surveyId, userid: userId}); + }).then((entry) => { + entry.answers = this.textUtils.parseJSON(entry.answers); - return entry; - }); + return entry; }); } @@ -171,7 +171,7 @@ export class AddonModSurveyOfflineProvider { timecreated: new Date().getTime() }; - return site.getDb().insertRecord(this.SURVEY_TABLE, entry); + return site.getDb().insertRecord(AddonModSurveyOfflineProvider.SURVEY_TABLE, entry); }); } } diff --git a/src/addon/mod/survey/survey.module.ts b/src/addon/mod/survey/survey.module.ts index 69b7a52bc..0762e6154 100644 --- a/src/addon/mod/survey/survey.module.ts +++ b/src/addon/mod/survey/survey.module.ts @@ -26,6 +26,7 @@ import { AddonModSurveyPrefetchHandler } from './providers/prefetch-handler'; import { AddonModSurveySyncProvider } from './providers/sync'; import { AddonModSurveySyncCronHandler } from './providers/sync-cron-handler'; import { AddonModSurveyOfflineProvider } from './providers/offline'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_SURVEY_PROVIDERS: any[] = [ @@ -52,10 +53,23 @@ export class AddonModSurveyModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModSurveyModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModSurveyPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModSurveyLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModSurveySyncCronHandler) { + cronDelegate: CoreCronDelegate, syncHandler: AddonModSurveySyncCronHandler, updateManager: CoreUpdateManagerProvider) { + moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); contentLinksDelegate.registerHandler(linkHandler); cronDelegate.register(syncHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_mod_survey_answers', + newName: AddonModSurveyOfflineProvider.SURVEY_TABLE, + fields: [ + { + name: 'answers', + type: 'object' + } + ] + }); } } diff --git a/src/addon/mod/wiki/providers/wiki-offline.ts b/src/addon/mod/wiki/providers/wiki-offline.ts index 7ea26138f..6ae9935d4 100644 --- a/src/addon/mod/wiki/providers/wiki-offline.ts +++ b/src/addon/mod/wiki/providers/wiki-offline.ts @@ -25,10 +25,10 @@ export class AddonModWikiOfflineProvider { protected logger; // Variables for database. - protected NEW_PAGES_TABLE = 'addon_mod_wiki_new_pages_store'; + static NEW_PAGES_TABLE = 'addon_mod_wiki_new_pages_store'; protected tablesSchema = [ { - name: this.NEW_PAGES_TABLE, + name: AddonModWikiOfflineProvider.NEW_PAGES_TABLE, columns: [ { name: 'wikiid', @@ -117,7 +117,7 @@ export class AddonModWikiOfflineProvider { userId = this.convertToPositiveNumber(userId); groupId = this.convertToPositiveNumber(groupId); - return site.getDb().deleteRecords(this.NEW_PAGES_TABLE, { + return site.getDb().deleteRecords(AddonModWikiOfflineProvider.NEW_PAGES_TABLE, { subwikiid: subwikiId, wikiid: wikiId, userid: userId, @@ -135,7 +135,7 @@ export class AddonModWikiOfflineProvider { */ getAllNewPages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getAllRecords(this.NEW_PAGES_TABLE); + return site.getDb().getAllRecords(AddonModWikiOfflineProvider.NEW_PAGES_TABLE); }); } @@ -160,7 +160,7 @@ export class AddonModWikiOfflineProvider { userId = this.convertToPositiveNumber(userId); groupId = this.convertToPositiveNumber(groupId); - return site.getDb().getRecord(this.NEW_PAGES_TABLE, { + return site.getDb().getRecord(AddonModWikiOfflineProvider.NEW_PAGES_TABLE, { subwikiid: subwikiId, wikiid: wikiId, userid: userId, @@ -188,7 +188,7 @@ export class AddonModWikiOfflineProvider { userId = this.convertToPositiveNumber(userId); groupId = this.convertToPositiveNumber(groupId); - return site.getDb().getRecords(this.NEW_PAGES_TABLE, { + return site.getDb().getRecords(AddonModWikiOfflineProvider.NEW_PAGES_TABLE, { subwikiid: subwikiId, wikiid: wikiId, userid: userId, @@ -247,10 +247,10 @@ export class AddonModWikiOfflineProvider { contentformat: 'html', timecreated: now, timemodified: now, - caneditpage: true + caneditpage: 1 }; - return site.getDb().insertRecord(this.NEW_PAGES_TABLE, entry); + return site.getDb().insertRecord(AddonModWikiOfflineProvider.NEW_PAGES_TABLE, entry); }); } diff --git a/src/addon/mod/wiki/wiki.module.ts b/src/addon/mod/wiki/wiki.module.ts index 8c9b4a2f1..c62188863 100644 --- a/src/addon/mod/wiki/wiki.module.ts +++ b/src/addon/mod/wiki/wiki.module.ts @@ -28,6 +28,7 @@ import { AddonModWikiIndexLinkHandler } from './providers/index-link-handler'; import { AddonModWikiPageOrMapLinkHandler } from './providers/page-or-map-link-handler'; import { AddonModWikiCreateLinkHandler } from './providers/create-link-handler'; import { AddonModWikiEditLinkHandler } from './providers/edit-link-handler'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_WIKI_PROVIDERS: any[] = [ @@ -57,7 +58,8 @@ export class AddonModWikiModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModWikiPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModWikiSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModWikiIndexLinkHandler, pageOrMapHandler: AddonModWikiPageOrMapLinkHandler, - createHandler: AddonModWikiCreateLinkHandler, editHandler: AddonModWikiEditLinkHandler) { + createHandler: AddonModWikiCreateLinkHandler, editHandler: AddonModWikiEditLinkHandler, + updateManager: CoreUpdateManagerProvider) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -66,5 +68,21 @@ export class AddonModWikiModule { linksDelegate.registerHandler(pageOrMapHandler); linksDelegate.registerHandler(createHandler); linksDelegate.registerHandler(editHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_mod_wiki_new_pages_store', + newName: AddonModWikiOfflineProvider.NEW_PAGES_TABLE, + fields: [ + { + name: 'subwikiWikiUserGroup', + delete: true + }, + { + name: 'caneditpage', + type: 'boolean' + } + ] + }); } } diff --git a/src/addon/notes/notes.module.ts b/src/addon/notes/notes.module.ts index bcd775301..259c7cbf5 100644 --- a/src/addon/notes/notes.module.ts +++ b/src/addon/notes/notes.module.ts @@ -23,6 +23,7 @@ import { AddonNotesComponentsModule } from './components/components.module'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; import { CoreCronDelegate } from '@providers/cron'; import { CoreUserDelegate } from '@core/user/providers/user-delegate'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_NOTES_PROVIDERS: any[] = [ @@ -46,10 +47,17 @@ export const ADDON_NOTES_PROVIDERS: any[] = [ export class AddonNotesModule { constructor(courseOptionsDelegate: CoreCourseOptionsDelegate, courseOptionHandler: AddonNotesCourseOptionHandler, userDelegate: CoreUserDelegate, userHandler: AddonNotesUserHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonNotesSyncCronHandler) { + cronDelegate: CoreCronDelegate, syncHandler: AddonNotesSyncCronHandler, updateManager: CoreUpdateManagerProvider) { + // Register handlers. courseOptionsDelegate.registerHandler(courseOptionHandler); userDelegate.registerHandler(userHandler); cronDelegate.register(syncHandler); + + // Allow migrating the tables from the old app to the new schema. + updateManager.registerSiteTableMigration({ + name: 'mma_notes_offline_notes', + newName: AddonNotesOfflineProvider.NOTES_TABLE + }); } } diff --git a/src/addon/notes/providers/notes-offline.ts b/src/addon/notes/providers/notes-offline.ts index 597458ef8..b75a69067 100644 --- a/src/addon/notes/providers/notes-offline.ts +++ b/src/addon/notes/providers/notes-offline.ts @@ -25,10 +25,10 @@ export class AddonNotesOfflineProvider { protected logger; // Variables for database. - protected NOTES_TABLE = 'addon_notes_offline_notes'; + static NOTES_TABLE = 'addon_notes_offline_notes'; protected tablesSchema = [ { - name: this.NOTES_TABLE, + name: AddonNotesOfflineProvider.NOTES_TABLE, columns: [ { name: 'userid', @@ -79,7 +79,7 @@ export class AddonNotesOfflineProvider { */ deleteNote(userId: number, content: string, timecreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.NOTES_TABLE, { + return site.getDb().deleteRecords(AddonNotesOfflineProvider.NOTES_TABLE, { userid: userId, content: content, created: timecreated @@ -95,7 +95,7 @@ export class AddonNotesOfflineProvider { */ getAllNotes(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.NOTES_TABLE); + return site.getDb().getRecords(AddonNotesOfflineProvider.NOTES_TABLE); }); } @@ -110,7 +110,7 @@ export class AddonNotesOfflineProvider { */ getNote(userId: number, content: string, timecreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.NOTES_TABLE, { + return site.getDb().getRecord(AddonNotesOfflineProvider.NOTES_TABLE, { userid: userId, content: content, created: timecreated @@ -127,7 +127,7 @@ export class AddonNotesOfflineProvider { */ getNotesForCourse(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.NOTES_TABLE, {courseid: courseId}); + return site.getDb().getRecords(AddonNotesOfflineProvider.NOTES_TABLE, {courseid: courseId}); }); } @@ -140,7 +140,7 @@ export class AddonNotesOfflineProvider { */ getNotesForUser(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.NOTES_TABLE, {userid: userId}); + return site.getDb().getRecords(AddonNotesOfflineProvider.NOTES_TABLE, {userid: userId}); }); } @@ -153,7 +153,7 @@ export class AddonNotesOfflineProvider { */ getNotesWithPublishState(state: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.NOTES_TABLE, {publishstate: state}); + return site.getDb().getRecords(AddonNotesOfflineProvider.NOTES_TABLE, {publishstate: state}); }); } @@ -219,7 +219,7 @@ export class AddonNotesOfflineProvider { lastmodified: now }; - return site.getDb().insertRecord(this.NOTES_TABLE, data).then(() => { + return site.getDb().insertRecord(AddonNotesOfflineProvider.NOTES_TABLE, data).then(() => { return data; }); }); diff --git a/src/addon/notifications/pages/settings/settings.ts b/src/addon/notifications/pages/settings/settings.ts index cf1d19ee3..ed4cabc45 100644 --- a/src/addon/notifications/pages/settings/settings.ts +++ b/src/addon/notifications/pages/settings/settings.ts @@ -58,7 +58,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { this.canChangeSound = localNotificationsProvider.isAvailable() && !appProvider.isDesktop(); if (this.canChangeSound) { configProvider.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true).then((enabled) => { - this.notificationSound = enabled; + this.notificationSound = !!enabled; }); } } @@ -245,7 +245,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { * @param {enabled} enabled True to enable the notification sound, false to disable it. */ changeNotificationSound(enabled: boolean): void { - this.configProvider.set(CoreConstants.SETTINGS_NOTIFICATION_SOUND, enabled).finally(() => { + this.configProvider.set(CoreConstants.SETTINGS_NOTIFICATION_SOUND, enabled ? 1 : 0).finally(() => { const siteId = this.sitesProvider.getCurrentSiteId(); this.eventsProvider.trigger(CoreEventsProvider.NOTIFICATION_SOUND_CHANGED, {enabled}, siteId); this.localNotificationsProvider.rescheduleAll(); diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 74bb4ec75..444a7b1c9 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -33,17 +33,16 @@ import { CoreConfigConstants } from '../../../configconstants'; */ @Injectable() export class AddonPushNotificationsProvider { - protected logger; protected pushID: string; protected appDB: any; static COMPONENT = 'AddonPushNotificationsProvider'; // Variables for database. - protected BADGE_TABLE = 'addon_pushnotifications_badge'; + static BADGE_TABLE = 'addon_pushnotifications_badge'; protected tablesSchema = [ { - name: this.BADGE_TABLE, + name: AddonPushNotificationsProvider.BADGE_TABLE, columns: [ { name: 'siteid', @@ -79,7 +78,7 @@ export class AddonPushNotificationsProvider { * @return {Promise} Resolved when done. */ cleanSiteCounters(siteId: string): Promise { - return this.appDB.deleteRecords(this.BADGE_TABLE, {siteid: siteId} ).finally(() => { + return this.appDB.deleteRecords(AddonPushNotificationsProvider.BADGE_TABLE, {siteid: siteId} ).finally(() => { this.updateAppCounter(); }); } @@ -383,7 +382,7 @@ export class AddonPushNotificationsProvider { * @return {Promise} Promise resolved with the stored badge counter for the addon or site or 0 if none. */ protected getAddonBadge(siteId?: string, addon: string = 'site'): Promise { - return this.appDB.getRecord(this.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => { + return this.appDB.getRecord(AddonPushNotificationsProvider.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => { return (entry && entry.number) || 0; }).catch(() => { return 0; @@ -407,7 +406,7 @@ export class AddonPushNotificationsProvider { number: value }; - return this.appDB.insertRecord(this.BADGE_TABLE, entry).then(() => { + return this.appDB.insertRecord(AddonPushNotificationsProvider.BADGE_TABLE, entry).then(() => { return value; }); } diff --git a/src/addon/pushnotifications/pushnotifications.module.ts b/src/addon/pushnotifications/pushnotifications.module.ts index 6c94bec97..a212c0c80 100644 --- a/src/addon/pushnotifications/pushnotifications.module.ts +++ b/src/addon/pushnotifications/pushnotifications.module.ts @@ -19,6 +19,7 @@ import { AddonPushNotificationsDelegate } from './providers/delegate'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; +import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_PUSHNOTIFICATIONS_PROVIDERS: any[] = [ @@ -35,7 +36,8 @@ export const ADDON_PUSHNOTIFICATIONS_PROVIDERS: any[] = [ }) export class AddonPushNotificationsModule { constructor(platform: Platform, pushNotificationsProvider: AddonPushNotificationsProvider, eventsProvider: CoreEventsProvider, - localNotificationsProvider: CoreLocalNotificationsProvider, loggerProvider: CoreLoggerProvider) { + localNotificationsProvider: CoreLocalNotificationsProvider, loggerProvider: CoreLoggerProvider, + updateManager: CoreUpdateManagerProvider) { const logger = loggerProvider.getInstance('AddonPushNotificationsModule'); @@ -66,5 +68,17 @@ export class AddonPushNotificationsModule { // Listen for local notification clicks (generated by the app). localNotificationsProvider.registerClick(AddonPushNotificationsProvider.COMPONENT, pushNotificationsProvider.notificationClicked); + + // Allow migrating the table from the old app to the new schema. + updateManager.registerAppTableMigration({ + name: 'mma_pushnotifications_badge', + newName: AddonPushNotificationsProvider.BADGE_TABLE, + fields: [ + { + name: 'siteid', + newName: 'siteId' + } + ] + }); } } diff --git a/src/assets/ydn.db-dev.js b/src/assets/ydn.db-dev.js new file mode 100755 index 000000000..d9a1b3958 --- /dev/null +++ b/src/assets/ydn.db-dev.js @@ -0,0 +1,365 @@ +(function(){var l,s=this;function t(a){return void 0!==a}function ba(a){a=a.split(".");for(var b=s,c;c=a.shift();)if(null!=b[c])b=b[c];else return null;return b} +function ca(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; +else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function u(a){return"array"==ca(a)}function v(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length}function w(a){return"string"==typeof a}function da(a){return"boolean"==typeof a}function ea(a){return"number"==typeof a}function fa(a){return"function"==ca(a)}function y(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}var ga="closure_uid_"+(1E9*Math.random()>>>0),ha=0; +function ia(a,b,c){return a.call.apply(a.bind,arguments)}function ja(a,b,c){if(!a)throw Error();if(2":"
")} +function va(a){if(!wa.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(xa,"&"));-1!=a.indexOf("<")&&(a=a.replace(ya,"<"));-1!=a.indexOf(">")&&(a=a.replace(za,">"));-1!=a.indexOf('"')&&(a=a.replace(Aa,"""));-1!=a.indexOf("'")&&(a=a.replace(Ba,"'"));-1!=a.indexOf("\x00")&&(a=a.replace(Ca,"�"));return a}var xa=/&/g,ya=//g,Aa=/"/g,Ba=/'/g,Ca=/\x00/g,wa=/[\x00&<>"']/;function Da(a){return ua(a.replace(/ /g,"  "),void 0)} +function Ea(a,b){for(var c=b.length,d=0;de))if(d in Ga)d=Ga[d];else if(d in Fa)d=Ga[d]=Fa[d];else{e=d;g=d.charCodeAt(0);if(31g)e=d;else{if(256>g){if(e="\\x",16>g||256g&&(e+="0");e+=g.toString(16).toUpperCase()}d=Ga[d]=e}g=d}b[f]=g}b.push('"');return b.join("")}function Ia(a,b){return ab?1:0};function Ja(a,b){b.unshift(a);qa.call(this,sa.apply(null,b));b.shift()}z(Ja,qa);Ja.prototype.name="AssertionError";function Ka(a,b){throw new Ja("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1));};function La(a){s.setTimeout(function(){throw a;},0)}var Ma; +function Na(){if(s.Promise&&s.Promise.resolve){var a=s.Promise.resolve();return function(b){a.then(function(){try{b()}catch(a){La(a)}})}}var b=s.MessageChannel;"undefined"===typeof b&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(b=function(){var a=document.createElement("iframe");a.style.display="none";a.src="";document.documentElement.appendChild(a);var b=a.contentWindow,a=b.document;a.open();a.write("");a.close();var c="callImmediate"+Math.random(),d=b.location.protocol+ +"//"+b.location.host,a=ka(function(a){if(a.origin==d||a.data==c)this.port1.onmessage()},this);b.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){b.postMessage(c,d)}}});if("undefined"!==typeof b){var c=new b,d={},e=d;c.port1.onmessage=function(){d=d.next;var a=d.Eb;d.Eb=null;a()};return function(a){e.next={Eb:a};e=e.next;c.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(a){var b=document.createElement("script"); +b.onreadystatechange=function(){b.onreadystatechange=null;b.parentNode.removeChild(b);b=null;a();a=null};document.documentElement.appendChild(b)}:function(a){s.setTimeout(a,0)}};function Oa(a,b){if(!Pa){var c=Qa;fa(s.setImmediate)?s.setImmediate(c):(Ma||(Ma=Na()),Ma(c));Pa=!0}Ra.push(new Sa(a,b))}var Pa=!1,Ra=[];[].push(function(){Pa=!1;Ra=[]});function Qa(){for(;Ra.length;){var a=Ra;Ra=[];for(var b=0;bc?Math.max(0,a.length+c):c;if(w(a))return w(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc?null:w(a)?a.charAt(c):a[c]}function ob(a,b,c){for(var d=a.length,e=w(a)?a.split(""):a,f=0;f=a.length)throw Qb;if(b in a)return a[b++];b++}};return c}throw Error("Not implemented");} +function Tb(a,b){if(v(a))try{kb(a,b,void 0)}catch(c){if(c!==Qb)throw c;}else{a=Sb(a);try{for(;;)b.call(void 0,a.next(),void 0,a)}catch(d){if(d!==Qb)throw d;}}};function Ub(a,b){this.b={};this.a=[];this.d=this.c=0;var c=arguments.length;if(1=c.length)throw Qb;var g=c[b++];return a?g:d[g]}};return g};var Xb;a:{var Yb=s.navigator;if(Yb){var Zb=Yb.userAgent;if(Zb){Xb=Zb;break a}}Xb=""}function $b(a){return-1!=Xb.indexOf(a)};function ac(){return s.navigator||null}var bc=$b("Opera")||$b("OPR"),cc=$b("Trident")||$b("MSIE"),dc=$b("Gecko")&&-1==Xb.toLowerCase().indexOf("webkit")&&!($b("Trident")||$b("MSIE")),ec=-1!=Xb.toLowerCase().indexOf("webkit");ec&&$b("Mobile");var fc,gc=ac();fc=gc&&gc.platform||"";fc.indexOf("Mac");fc.indexOf("Win");fc.indexOf("Linux");ac()&&(ac().appVersion||"").indexOf("X11");var hc=Xb;hc&&hc.indexOf("Android");hc&&hc.indexOf("iPhone");hc&&hc.indexOf("iPad"); +function ic(){var a=s.document;return a?a.documentMode:void 0}var jc=function(){var a="",b;if(bc&&s.opera)return a=s.opera.version,fa(a)?a():a;dc?b=/rv\:([^\);]+)(\)|;)/:cc?b=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:ec&&(b=/WebKit\/(\S+)/);b&&(a=(a=b.exec(Xb))?a[1]:"");return cc&&(b=ic(),b>parseFloat(a))?String(b):a}(),kc={}; +function lc(a){var b;if(!(b=kc[a])){b=0;for(var c=String(jc).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),d=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=Math.max(c.length,d.length),f=0;0==b&&fb.length){c.push(qc(a)+"(");for(var d=a.arguments,e=0;d&&e=Gc(this).value)for(fa(b)&&(b=b()),a=this.f(a,b,c,uc.prototype.log),b="log:"+a.d,s.console&&(s.console.timeStamp?s.console.timeStamp(b):s.console.markTimeline&&s.console.markTimeline(b)),s.msWriteProfilerMark&&s.msWriteProfilerMark(b),b=this;b;){c=b;var d=a;if(c.a)for(var e=0,f=void 0;f=c.a[e];e++)f(d);b=b.c}}; +uc.prototype.f=function(a,b,c,d){a=new sc(a,String(b),this.e);if(c){a.b=c;var e;d=d||uc.prototype.f;try{var f;var g=ba("window.location.href");if(w(c))f={message:c,name:"Unknown error",lineNumber:"Not available",fileName:g,stack:"Not available"};else{var h,k;b=!1;try{h=c.lineNumber||c.sc||"Not available"}catch(m){h="Not available",b=!0}try{k=c.fileName||c.filename||c.sourceURL||s.$googDebugFname||g}catch(n){k="Not available",b=!0}f=!b&&c.lineNumber&&c.fileName&&c.stack&&c.message&&c.name?c:{message:c.message|| +"Not available",name:c.name||"UnknownError",lineNumber:h,fileName:k,stack:c.stack||"Not available"}}e="Message: "+va(f.message)+'\nUrl: '+f.fileName+"\nLine: "+f.lineNumber+"\n\nBrowser stack:\n"+va(f.stack+"-> ")+"[end]\n\nJS stack traversal:\n"+va(oc(d)+"-> ")}catch(q){e="Exception trying to expose exception! You win, we lose. "+q}a.a=e}return a};var Hc={},Ic=null;function Jc(){Ic||(Ic=new uc(""),Hc[""]=Ic,Ic.b=Ac)} +function Kc(){Jc();return Ic}function G(a){Jc();var b;if(!(b=Hc[a])){b=new uc(a);var c=a.lastIndexOf("."),d=a.substr(c+1),c=G(a.substr(0,c));c.d||(c.d={});c.d[d]=b;b.c=c;Hc[a]=b}return b};function Lc(a,b,c){a&&a.log(b,c,void 0)}function Mc(a,b){a&&a.log(xc,b,void 0)}function H(a,b){a&&a.log(yc,b,void 0)}function Nc(a,b){a&&a.log(Bc,b,void 0)};function I(a,b){a&&a.log(Dc,b,void 0)}function J(a,b){a&&a.log(Cc,b,void 0)};function Oc(a,b){B.call(this,0,b);this.e=[]}z(Oc,B);Oc.prototype.Va=function(a,b){this.e.push([a,b]);return this};Oc.prototype.callback=function(a){this.e.length=0;Oc.r.callback.call(this,a)};Oc.prototype.k=function(a){this.e.length=0;Oc.r.k.call(this,a)};B.prototype.done=B.prototype.C;B.prototype.fail=B.prototype.Db;B.prototype.always=B.prototype.Cb;Oc.prototype.then=Oc.prototype.then;function K(a,b,c,d){this.lower=a;this.upper=b;this.lowerOpen=!!c;this.upperOpen=!!d;fa(Object.freeze)&&Object.freeze(this)}K.prototype.lower=void 0;K.prototype.upper=void 0;K.prototype.toJSON=function(){return Pc(this)};function Qc(a){return Rc(a)}K.only=function(a){return new K(a,a,!1,!1)};K.bound=function(a,b,c,d){return new K(a,b,c,d)};K.upperBound=function(a,b){return new K(void 0,a,void 0,!!b)};K.lowerBound=function(a,b){return new K(a,void 0,!!b,void 0)}; +function Sc(a){var b;if(u(a))b=sb(a),b.push("\uffff");else if(w(a))b=a+"\uffff";else if(ea(a))b=a+2.220460492503131E-16,a-=2.220460492503131E-16;else return K.only(a);return K.bound(a,b,!1,!0)}function Pc(a){a=a||{};return{lower:a.lower,upper:a.upper,lowerOpen:a.lowerOpen,upperOpen:a.upperOpen}} +function Rc(a){return null!=a?null!=a.upper&&null!=a.lower?Tc.bound(a.lower,a.upper,!!a.lowerOpen,!!a.upperOpen):null!=a.upper?Tc.upperBound(a.upper,a.upperOpen):null!=a.lower?Tc.lowerBound(a.lower,a.lowerOpen):null:null}function Uc(a){if(a instanceof K)return"";if(null!=a){if(y(a)){for(var b in a){var c;if(c=a.hasOwnProperty(b))c=!(0<=jb(["lower","upper","lowerOpen","upperOpen"],b));if(c)return'invalid attribute "'+b+'" in key range object'}return""}return"key range must be an object"}return""} +K.prototype.ba=function(a){var b=this.lower,c=this.upper,d=this.lowerOpen,e=this.upperOpen;null!=a.lower&&(null==this.lower||a.lower>=this.lower)&&(b=a.lower,d=a.lowerOpen||this.lowerOpen);null!=a.upper&&(null==this.upper||a.upper<=this.upper)&&(c=a.upper,e=a.upperOpen||this.upperOpen);return K.bound(b,c,d,e)};function Vc(a){if(!a)return"";var b=a.lowerOpen?"(":"[";null!=a.lower&&(b+=a.lower+", ");null!=a.upper&&(b+=a.upper);return b+=a.upperOpen?")":"]"} +function Wc(a,b,c,d,e){if(c)if(c.lowerOpen||c.upperOpen||null==c.lower||null==c.upper||0!==L(c.lower,c.upper)){if(null!=c.lower){var f=c.lowerOpen?" > ":" >= ";d.push(a+f+"?");e.push(Xc(c.lower,b))}null!=c.upper&&(f=c.upperOpen?" < ":" <= ",d.push(a+f+"?"),e.push(Xc(c.upper,b)))}else d.push(a+" = ?"),e.push(Xc(c.lower,b))} +function Yc(a,b,c,d){var e,f,g,h;if("starts"==a||"^"==a)return Sc(b);if("<"==a||"<="==a)e=b,g="<"==a;else if(">"==a||">="==a)f=b,h=">"==a;else if("="==a||"=="==a)e=f=b;else throw new C("invalid op: "+a);if("<"==c||"<="==c)e=d,g="<"==c;else if(">"==c||">="==c)f=d,h=">"==c;else if(t(c))throw new C("invalid op2: "+c);return K.bound(f,e,h,g)}var Tc=s.IDBKeyRange||s.webkitIDBKeyRange||K;function Zc(a,b){var c,d;2==arguments.length&&w(arguments[1])?(c=!0,d=arguments[1].split(".")):d=(c=v(b))?b:arguments;for(c=c?0:1;c=f?c.write(f+1):16510>=f?(f-=127,c.write(128|f>>8,f&255)):c.write(192|f>>10, +f>>2|255,(f|3)<<6)}c.write(0)}else if(e===ad)a.write(0);else return"";c=0}for(b=a.a.length;"00"===a.a[--b];);a.a.length=++b;return a.toString()}function ed(a){for(var b=[],c=b,d=[],e,f,g=new fd(a);null!=gd(g);)if(0===g.a)c=d.pop();else{if(null===g.a)break;do{e=g.a/4|0;a=g.a%4;for(var h=0;hc&&(d=1,c=-c);f=0;if(2.2250738585072014E-308<=c){for(e=c;1>e;)f--,e*=2;for(;2<=e;)f++,e/=2;e=f+1023}f=e?Math.floor(4503599627370496*(c/Math.pow(2,f)-1)):Math.floor(c/4.9E-324)}else e=2047,isNaN(c)?f=0x8000000000000:-Infinity===c&&(d=1);c=d;d=e;e=f;c&&(e=0xfffffffffffff-e,d=2047-d);a.write((c?0:128)|d>>4);a.write((d&15)<<4|0|e/281474976710656);e%=281474976710656;c=0|e/4294967296;a.write(c>>8,c&255);e%=4294967296;c=0|e/65536;a.write(c>> +8,c&255);c=e%65536;a.write(c>>8,c&255)}function hd(a){var b=gd(a)|0,c=b>>7?!1:!0,d=c?-1:1,e=(b&127)<<4,b=gd(a)|0,e=e+(b>>4);c&&(e=2047-e);for(var b=[c?15-(b&15):b&15],f=6;f--;)b.push(c?255-(gd(a)|0):gd(a)|0);a=0;for(f=7;f--;)a=a/256+b[f];a/=16;return 0===a&&0===e?0:(a+1)*Math.pow(2,e-1023)*d} +function id(a){for(var b=[],c=0,d=0,e=0,f,g;;){f=gd(a);if(0===f||null==f)break;0===c?(g=f>>6,2>g&&!isNaN(f)?b.push(String.fromCharCode(f-1)):(c=g,d=f<<10,e++)):2===c?(b.push(String.fromCharCode(d+f+127)),c=d=e=0):2===e?(d+=f<<2,e++):(b.push(String.fromCharCode(d|f>>6)),c=d=e=0)}return b.join("")}function fd(a){this.a=null;this.b=a;this.c=this.b.length-1;this.index=-1}function gd(a){return a.a=a.indexd?1:c==d?0:-1};function kd(a,b,c,d,e){if(!(b instanceof K))if(w(b)&&t(c))b=Yc(b,c,d,e);else if(null!=b){if(!(b instanceof K))if(y(b))b=new K(b.lower,b.upper,b.lowerOpen,b.upperOpen);else throw new C("Invalid key range: "+b+" of type "+typeof b);}else b=null;this.a=b;this.ea=a}kd.prototype.ea="";kd.prototype.ba=function(a){if(this.ea!=a.ea)return null;a=null!=this.a&&null!=a.a?this.a.ba(a.a):this.a||a.a;return new kd(this.ea,a)};var ld={READ_ONLY:"readonly",READ_WRITE:"readwrite",VERSION_CHANGE:"versionchange"},md=s.IDBRequest&&"LOADING"in s.IDBRequest?s.IDBTransaction:s.webkitIDBRequest&&"LOADING"in s.webkitIDBRequest&&1===s.webkitIDBTransaction.READ_WRITE?s.webkitIDBTransaction:ld,nd=md.READ_ONLY,M=md.READ_WRITE,od=md.VERSION_CHANGE,pd=s.indexedDB||s.mozIndexedDB||s.webkitIndexedDB||s.moz_indexedDB||s.msIndexedDB;function qd(){0!=rd&&(sd[this[ga]||(this[ga]=++ha)]=this)}var rd=0,sd={};qd.prototype.xa=!1;qd.prototype.kb=function(){if(!this.xa&&(this.xa=!0,this.$(),0!=rd)){var a=this[ga]||(this[ga]=++ha);delete sd[a]}};qd.prototype.$=function(){if(this.Va)for(;this.Va.length;)this.Va.shift()()};function td(a,b,c,d){qd.call(this);this.b=c;this.m=c.getName();this.I=void 0;this.f=!1;this.p=null;this.d=a;this.Mb=b;this.ma=0;this.N=this.v=!1;this.u=d||4;this.h=this.c=this.a=void 0;this.i=function(){throw new Jb;};this.l=function(){throw new Jb;};this.Ya=function(){}}z(td,qd);l=td.prototype; +l.Bb=function(a,b,c,d,e){if(t(b)){a=this.b;var f,g=b;u(b)?(f=ud(a,b),g=b.join(", ")):f=vd(a,b);if(!f)throw new C('require index "'+g+'" not found in store "'+a.getName()+'"');this.I=f.getName()}this.f=w(this.I);this.p=c||null;this.ma=0;this.N=this.v=!1;this.reverse="prev"==d||"prevunique"==d;this.unique="nextunique"==d||"prevunique"==d;this.Q=d;this.wa=e;this.h=this.c=this.a=void 0};l.Q="";l.p=null;l.unique=!1;l.reverse=!1;l.wa=!0;l.logger=G("ydn.db.core.req.AbstractCursor"); +function wd(a,b){a.l(b);xd(a);a.v=!0}l.O=function(a,b,c){null==a&&(J(this.logger,this+" finished."),this.v=!0);this.a=a;this.c=b;this.h=c;this.ma++;this.v?(I(this.logger,this+" DONE."),this.i(),xd(this)):(I(this.logger,this+" new cursor position {"+(this.f?this.a+", "+this.c:this.a)+"}"),this.i(this.a))};l.$=function(){this.d=null};l.toString=function(){return"Cursor:"+this.m+(t(this.I)?":"+this.I:"")+"["+(this.d?"":"~")+this.Mb+"]"}; +function xd(a){null!=a.c?a.c=yd(a.c):a.c=void 0;null!=a.a?a.a=yd(a.a):a.a=void 0;a.Ya(a.N,a.a,a.c)}l.open=function(a,b,c,d){this.d=a;this.Mb=b;this.v=this.N=!1;this.a=c;this.c=d;this.openCursor(this.a,this.c)};function zd(a){a.N=!0;I(a.logger,a+": exit");xd(a)}l.mb=function(){return this.ma};l.nc=function(){return this.a};l.B=function(){return this.f?this.c:this.a};l.aa=function(){return this.wa?this.B():this.h};l.Da=function(){};l.ja=function(){}; +function Ad(a,b,c){I(a.logger,a+" restarting");a.v=!1;a.N=!1;a.openCursor(c,b)};function Bd(){};function N(a,b,c,d,e,f,g){if(!w(a))throw new TypeError("store name must be a string, but "+a+" found.");this.c=a;this.d=b;this.e=g;this.i=!!this.d;if(t(d)&&!da(d))throw new C("reverse value must be a boolean, but "+typeof d+" found");if(t(e)&&!da(e))throw new C("unique value must be a boolean, but "+typeof e+" found");if(t(f)&&!da(f))throw new C("key_only value must be a boolean, but "+typeof f+" found");this.a=t(f)?f:!!w(this.d);a="next";d&&e?a="prevunique":d?a="prev":e&&(a="nextunique");this.l= +a;if(d=Uc(c))throw new C("Invalid key range: "+d);this.b=Rc(c);this.f=Dd;this.o=NaN}z(N,Bd);N.prototype.a=!0;function Ed(a,b,c){if(3=a.g)?[]:b}$d.prototype.c=function(){return[]};function be(a){Error.captureStackTrace?Error.captureStackTrace(this,be):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ConstraintError"}z(be,Error);be.prototype.name="ConstraintError";be.prototype.toString=function(){return this.name+": "+this.message};function ce(a){Error.captureStackTrace?Error.captureStackTrace(this,ce):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.InvalidKeyException"}z(ce,Error); +ce.prototype.toString=function(){return this.name+": "+this.message};function de(a){Error.captureStackTrace?Error.captureStackTrace(this,de):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.VersionError"}z(de,Error);de.prototype.name="ydn.db.VersionError";de.prototype.toString=function(){return this.name+": "+this.message};function ee(a){Error.captureStackTrace?Error.captureStackTrace(this,ee):this.stack=Error().stack||"";a&&(this.message=String(a))}z(ee,Error); +ee.prototype.name="ydn.db.InternalError";function Ld(a){Error.captureStackTrace?Error.captureStackTrace(this,Ld):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="InvalidStateError"}z(Ld,Error);function fe(a){Error.captureStackTrace?Error.captureStackTrace(this,fe):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="InvalidAccessError"}z(fe,Error); +function ge(a){Error.captureStackTrace?Error.captureStackTrace(this,ge):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="NotFoundError"}z(ge,Error);ge.prototype.name="NotFoundError";ge.prototype.toString=function(){return this.name+": "+this.message};function he(a,b){Error.captureStackTrace?Error.captureStackTrace(this,he):this.stack=Error().stack||"";b&&(this.message=String(b));this.message+=" :"+a.message+" ["+a.code+"]";this.name="SQLError"}z(he,Error); +he.prototype.toString=function(){return this.name+": "+this.message};function ie(a,b){Error.captureStackTrace?Error.captureStackTrace(this,ie):this.stack=Error().stack||"";b&&(this.message=String(b));this.message+=" :"+a.message;this.name="SecurityError"}z(ie,Error);ie.prototype.toString=function(){return this.name+": "+this.message};function je(a){Error.captureStackTrace?Error.captureStackTrace(this,je):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.SqlParseError"} +z(je,Error);function ke(a){Error.captureStackTrace?Error.captureStackTrace(this,ke):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.TimeoutError"}z(ke,Error);function le(a,b,c){var d;if(y(a))d=a.store,b=a.id,null!=a.parent&&(c=new le(a.parent));else if(t(b))d=a;else if(d=a.lastIndexOf("^|"),b=a,0a.J.length)b=!1;else{for(c=0;c=this.i)throw new Ib("Exceed maximum number of transactions of "+this.i);this.l=!0;this.j().transaction(function(c){var d=h.b;d.a=c;d.d=!1;d.J=b;d.mode=g;d.b++;d.F=null;k=h.A();Nc(h.logger,k+" BEGIN "+U(b)+" "+g);a(h);for(a=null;ve(h);)c=h.c.shift(),c.F&&h.e.push(c.F),I(h.logger,"pop tx queue"+(h.c.length+1)+" reusing T"+h.d),c.Ma()},e,g,function(a,b){Nc(h.logger,k+" "+a);var c=h.b;c.a?(c.a=null,c.J=null,c.mode=null,fa(c.F)&&c.F(a,b),c.F=null):H(c.logger,c+" has no TX to be unlocked for "+ +a);for(c=0;cc.length)throw new C('at least one valid key required in key list "'+ +Ke(a)+'"');d=this.a.request("rm:keys",c,M);Q(d,function(){W(this).vb(d,a)},this)}else throw new C('first argument requires store name, key (ydn.db.Key) or list of keys (array) , but "'+Ke(a)+'" ('+ca(a)+") found.");return d};l.toString=function(){return"DbOperator:"+this.j().getName()};function Ze(a,b,c){ze.call(this,a,b,c)}z(Ze,Ie);l=Ze.prototype;l.logger=G("ydn.db.core.DbOperator");l.get=function(a,b){if(a instanceof N){var c=a.c,d=X(this.b,c);if(!d)throw new C('store "'+c+'" not found.');var e=a.d;if(t(e)&&!Pe(d,e))throw new C('index "'+e+'" not found in store "'+c+'".');J(this.logger,"getByIterator:"+a);var f=this.a.request("get:iter",[c]);Q(f,function(){$e(this,5,f,a,1)},this);return f}return Ze.r.get.call(this,a,b)}; +l.keys=function(a,b,c,d,e,f,g){if(a instanceof N){var h=100;if(ea(b)){if(h=b,1>h)throw new C("limit must be a positive value, but "+b);}else if(t(b))throw new C("limit must be a number, but "+b);if(t(c))throw new C("offset must not be specified");J(this.logger,"keysByIterator:"+a);var k=this.a.request("keys:iter",[a.c]);Q(k,function(){a.i?$e(this,1,k,a,h):$e(this,2,k,a,h)},this);return k}return Ze.r.keys.call(this,a,b,c,d,e,f,g)}; +l.count=function(a,b,c,d){if(a instanceof N){if(t(b)||t(c))throw new C("too many arguments.");J(this.logger,"countIterator:"+a);var e=this.a.request("count",[a.c]);Q(e,function(){$e(this,6,e,a)},this);return e}return Ze.r.count.call(this,a,b,c,d)}; +l.w=function(a,b,c,d,e,f){if(a instanceof N){var g;if(ea(b)){if(g=b,1>g)throw new C("limit must be a positive value, but "+g);}else if(t(b))throw new C("limit must be a number, but "+b);if(t(c))throw new C("offset must not be specified");J(this.logger,"listByIterator:"+a);var h=this.a.request("values:iter",[a.c]);Q(h,function(){a.a?$e(this,2,h,a,g):$e(this,4,h,a,g)},this);return h}return Ze.r.w.call(this,a,b,c,d,e,f)}; +l.Ab=function(a,b,c){if(!u(b))throw new C("iterators argument must be an array, but "+b+" of type "+typeof b+" found");for(var d=0;d",r=g?r+" ":r+"= ";g=Xc(f,n.type);h=Xc(h,a.type);n="";e?(a=tf(a,c,b,q,e,k,m),a.n+=" AND ",n=d+r+"?",c.push(g)):(e=k?K.upperBound(f,!0):K.lowerBound(f,!0),a=tf(a,c,b,q,e,k,m),n=a.n,a.n="");a.n+="("+n+" OR ("+d+" = ? AND "+p+r+"?))";c.push(g);c.push(h);return"SELECT "+a.select+" FROM "+a.M+" WHERE "+a.n+(a.group?" GROUP BY "+a.group:"")+" ORDER BY "+a.P}l.clone=function(){return Ce(this.toJSON())}; +l.index=function(a){return this.a[a]||null};function vd(a,b){return nb(a.a,function(a){return a.getName()==b})}function ud(a,b){for(var c=0;c +c)throw new C("Invalid version: "+c+" ("+a+")");isNaN(c)&&(c=void 0)}if(t(b)&&(!u(b)||0c.a.length)c=!1;else{for(var d=0;d=this.h)throw new Ib("Exceed maximum number of transactions of "+this.h);this.j().transaction(e,b,h,f)}}; +l.request=function(a,b,c,d){var e=new O(a),f=this;this.ha(function(a){f.a++;Id(e,a,f.A()+"R"+f.a)},b,c||nd,function(a,b){Kd(e);d&&d(a,b)});return e};l.W=function(a,b,c,d,e){var f=this,g;this.ha(function(c){f.a++;g=f.A()+"R"+f.a;J(f.logger,g+" BEGIN");b(c,g,function(b,d){f.c=c;g=f.A()+"R"+f.a;d?(J(f.logger,g+" ERROR"),a.k(b)):(J(f.logger,g+" SUCCESS"),a.callback(b));f.c=null});b=null;J(f.logger,g+" END")},c,d,e)};l.toString=function(){return"Parallel:"+this.f+":"+this.A()+(this.c?"*":"")};function Of(a,b){Nf.call(this,a,b,re)}z(Of,Nf);l=Of.prototype;l.logger=G("ydn.db.tr.AtomicParallel");l.Qb=function(){return!1};l.request=function(a,b,c){var d,e,f,g=this,h=Of.r.request.call(this,a,b,c,function(a,b){Kd(h);J(g.logger,"transaction "+a);if(d)"complete"!=a&&(f=!0,e=b),d(e,f);else{var c=new ke;P(h,c,!0)}});Md(h,function(a,b,c){f=b;e=a;d=c});return h}; +l.W=function(a,b,c,d,e){var f,g,h=new B;yb(h,function(a){g=!1;f=a},function(a){g=!0;f=a});Of.r.W.call(this,h,b,c,d,function(b,c){if("complete"!=b)a.k(c);else if(!0===g)a.k(f);else if(!1===g)a.callback(f);else{var d=new ke;a.k(d)}e&&(e(b,c),e=void 0)})};l.toString=function(){return"Atomic"+Of.r.toString.call(this)};function Pf(a,b,c){T.call(this,a,b,c);this.na=0;a=!0;b=re;c&&(t(c.isSerial)&&(a=!!c.isSerial),c.policy&&(b=c.policy));c=Qf(this,b,a);this.i=Qf(this,"atomic",!1);this.c=this.la(c,this.i)}z(Pf,T);l=Pf.prototype;l.na=0;l.Rb=function(a,b,c,d,e,f){a=a||re;var g;"readonly"==d?g=nd:"readwrite"==d&&(g=M);a=Qf(this,a,b,c,g,e);return this.la(a,f?null:this.i)};l.la=function(a){return new ze(this,this.a,a)}; +function Qf(a,b,c,d,e,f){if(c){if("multi"==b||"repeat"==b||"all"==b||b==re)return new te(a,a.na++,b,d,e,f);if("atomic"==b)return new ye(a,a.na++);throw new C('Invalid requestType "'+b+'"');}if("multi"==b||"repeat"==b||"all"==b||b==re)return new Nf(a,a.na++,b,d,e,f);if("atomic"==b)return new Of(a,a.na++);throw new C('Invalid requestType "'+b+'"');} +l.Zb=function(a,b,c){if(3r&&rk&&k=b.length)0b&&bf&&f=a)return Yf(c,b)}var c=this,d=this.e?-1:0;this.reverse?Zf(this.ca,b,this.e):$f(this.ca,b,this.e)}; +l.ja=function(a){if(null!=a){var b=this,c=new Z(a),d=function(c){b.e=c;if(!c)return Yf(b,c);var d=L(c.value.key,a);if(b.reverse){if(1!=d)return Yf(b,c)}else if(-1!=d)return Yf(b,c)};this.reverse?Zf(this.ca,d,c):$f(this.ca,d,c)}else this.advance(1)};function ag(a){setTimeout(function(){a.o.a?(a.o.a=!1,a.O(a.a,a.c,a.h),ag(a)):(a.g(),a.g=null)},4)} +function Yf(a,b){if(a.e=b){var c=b.value;if(a.p)if(a.reverse||null==a.p.upper)a.reverse&&null!=a.p.lower&&(d=L(c.key,a.p.lower),-1==d||0==d&&a.p.lowerOpen)&&(a.e=null);else{var d=L(c.key,a.p.upper);if(1==d||0==d&&a.p.upperOpen)a.e=null}if(a.e){if(a.unique&&null!=a.a&&null!=c.key&&0==L(a.a,c.key))return;a.a=c.key;a.c=a.f?c.a:a.a;4==a.u&&(a.wa?a.h=a.c:a.h=bg(a.Za,a.c))}}a.e||(a.a=void 0,a.c=void 0,a.h=void 0);return a.o.a=!0} +l.openCursor=function(a,b){var c=null;if(this.p)if(this.reverse){var d=this.f?"\uffff":void 0;null!=this.p.upper&&(c=new Z(this.p.upper,d))}else null!=this.p.lower&&(c=new Z(this.p.lower));null!=a&&(c=this.f?new Z(a,b):new Z(a));this.g=this.d.j(function(b){function d(b){var e=b.value,f=e.key;if(b&&null!=f)if(null!=a){if(0==cg(c,e))return}else if(this.p&&(!this.reverse&&this.p.lowerOpen&&null!=this.p.lower&&(e=L(f,this.p.lower),0==e)||this.reverse&&this.p.upperOpen&&null!=this.p.upper&&(e=L(f,this.p.upper), +0==e)))return;return Yf(this,b)}this.Za=dg(b,this.m);this.ca=eg(this.Za,this.I);this.reverse?Zf(this.ca,ka(d,this),c):$f(this.ca,ka(d,this),c);ag(this)},this)};l.clear=function(){throw new Hb;};l.Da=function(){throw new Hb;};l.toString=function(){return"Simple"+Wf.r.toString.call(this)};function fg(a,b,c){a=["ydn.db",a];t(b)&&(a.push(b),t(c)&&(a.push(c),t(void 0)&&a.push(bd(void 0))));return a.join("^|")};function Z(a,b){this.key=a;this.a=b}Z.prototype.toString=function(){return"ydn.db.con.simple.Node("+this.key+(null!=this.a?", "+this.a+")":")")};function cg(a,b){var c=L(a.key,b.key);return 0===c?null!=a.a?null!=b.a?L(a.a,b.a):1:null!=b.a?-1:0:c};function gg(a){this.L=a||hg}function hg(a,b){return String(a)String(b)?1:0}l=gg.prototype;l.s=null;l.L=null;l.U=null;l.T=null; +l.add=function(a){if(null==this.s)return this.T=this.U=this.s=new ig(a),!0;var b=null;jg(this,function(c){var d=null,e=this.L(c.value,a);0e&&(d=c.right,null==c.right&&(b=new ig(a,c),c.right=b,c==this.T&&(this.T=b)));return d});b&&(jg(this,function(a){a.count++;return a.parent},b.parent),kg(this,b.parent));return!!b}; +function lg(a,b){jg(a,function(a){var d=null,e=this.L(a.value,b);0e?d=a.right:mg(this,a);return d})}l.clear=function(){this.T=this.U=this.s=null};l.contains=function(a){var b=!1;jg(this,function(c){var d=null,e=this.L(c.value,a);0e?d=c.right:b=!0;return d});return b};l.indexOf=function(a){var b=-1,c=0;jg(this,function(d){var e=this.L(d.value,a);if(0e)return c++,d.right;b=c;return null});return b}; +l.mb=function(){return this.s?this.s.count:0};l.Oa=function(){var a=[];ng(this,function(b){a.push(b)});return a};function ng(a,b){if(a.s){var c,d=c=og(a);for(c=c.left?c.left:c;null!=d;)if(null!=d.left&&d.left!=c&&d.right!=c)d=d.left;else{if(d.right!=c&&b(d.value))break;var e=d,d=null!=d.right&&d.right!=c?d.right:d.parent;c=e}}}function jg(a,b,c){for(c=c?c:a.s;c&&null!=c;)c=b.call(a,c)} +function kg(a,b){jg(a,function(a){var b=a.left?a.left.height:0,e=a.right?a.right.height:0;1this.L(a.value,c)?b=a.right:d=a;return b}),!d)return}else d=og(a);a=d;for(var e=d.left?d.left:d;null!=a;)if(null!=a.left&&a.left!=e&&a.right!=e)a=a.left;else{if(a.right!=e&&b(a))return;var f=a;a=null!=a.right&&a.right!=e?a.right:a.parent;e=f}b(null)}} +function Zf(a,b,c){if(a.s){var d;if(c instanceof ig)d=c;else if(c){if(jg(a,ka(function(a){var b=null;0this.L(a.value,c)&&(b=a.right),d=a);return b},a)),!d)return}else d=tg(a);a=d;for(var e=d.right?d.right:d;null!=a;)if(null!=a.right&&a.right!=e&&a.left!=e)a=a.right;else{if(a.left!=e&&b(a))return;var f=a;a=null!=a.left&&a.left!=e?a.left:a.parent;e=f}b(null)}};function vg(a,b,c){this.f=a;this.storage=b;this.b=c;this.a={};a=this.b.keyPath;this.c=u(a)?a.join(","):a||"_ROWID_";this.a[this.c]=null;this.d=fg(this.f,this.b.getName(),this.c)+"^|"} +function eg(a,b){var c=b||a.c;if(!a.a[c]){a.a[c]=new ug(cg);for(var d=a.storage.length,e=0;eg.length&&(g.push(b.key),h.push(bf(p))))}},d);if(10>g.length)for(c=0;c=f)return!0}}var n=[],q,p=!!k&&null!=k[0];c=c||a.c;var r=c!=a.c;c=eg(a,c);var x=null,A=null;t(g)||(g=0);var E=-1,D=!1,F=!1;null!=d&&(null!=d.lower&&(x=r&&e?new Z(d.lower,"\uffff"):new Z(d.lower)),null!=d.upper&&(A=r&&!e?new Z(d.upper,"\uffff"):new Z(d.upper)),D=!!d.lowerOpen,F=!!d.upperOpen);if(p){e?F=!0:D=!0;d=k[0];var R=t(k[1])?k[1]:"\uffff";e?A=r?new Z(d,R):new Z(d):x=r?new Z(d,R):new Z(d)}e? +Zf(c,m,A):$f(c,m,x);return n}vg.prototype.fa=function(a,b,c,d,e){return Bg(this,2,a,b,c,d,e)};vg.prototype.toString=function(){return"ydn.db.con.simple.Store:"+this.f+":"+this.b.getName()};function Cg(){this.clear()}l=Cg.prototype;l.ib=function(){return this};l.setItem=function(a,b){t(this.a[a])||(this.keys.push(a.toString()),this.length=this.keys.length);this.a[a]=b};l.getItem=function(a){return t(this.a[a])?this.a[a]:null};l.removeItem=function(a){delete this.a[a];qb(this.keys,a.toString());this.length=this.keys.length};l.length=0;l.key=function(a){a=this.keys[a];return t(a)?this.a[a]:null};l.clear=function(){this.a={};this.keys=[];this.length=0};function Dg(a){this.e=a||new Cg;this.f={}}function dg(a,b){var c=X(a.a,b);if(c)a.f[b]||(a.f[b]=new vg(a.d,a.c,c));else throw new Jb('store name "'+b+'" not found.');return a.f[b]}Dg.prototype.K=function(a){var b=this;setTimeout(function(){var c=fg(b.d),c=b.c.getItem(c),c=new Df(c);a(c)},10)};function Eg(a,b){this.b=a;this.a=b}Eg.prototype.j=function(a,b){var c=this.b;setTimeout(function(){a.call(b,c)},4);var d=this;return function(){d.a("complete",null);d.a=null;d.b=null}};function Fg(a,b){this.a=b}z(Fg,ne);l=Fg.prototype;l.logger=G("ydn.db.crud.req.SimpleStore");l.sb=function(a,b,c){this.S(a,!0,!1,null,b,c)}; +l.S=function(a,b,c,d,e,f){I(this.logger,S(a)+" "+(b?"put":"add")+"Object"+(c?"":"s "+e.length+" objects"));var g=a.a.j(function(h){var k;if(c)k=dg(h,d),h=f?f[0]:void 0,h=Xf(k,h,e[0],!b),null!=h?P(a,h):(k=Ke(h),k=new be(k),P(a,k,!0));else{for(var m=d,n=[],q=!1,p=f||{},r=0;rp.rowsAffected&&(r=!0,aa=new be(aa+" no-op"));for(var ib=0,Cd=h.a.length;ibb&&b +b&&bh&&h=b.length)P(a,f,g);else{var k=X(e.a,b[h].m),m=Xc(b[h].id,k.type),n=" WHERE "+k.d+" = ?",q="DELETE FROM "+uf(k)+n,p=S(a)+" SQL: "+q+" PARAMS: "+[m];d.executeSql(q,[m],function(){f++;c(h)},function(a,b){H(e.logger,"error: "+p+b.message);g=!0;c(h);return!1});h++;for(var q=function(b){b="ydn.db.me:"+k.getName()+":"+b.getName();b="DELETE FROM "+Ha(b)+n;I(e.logger,S(a)+NaN+b);d.executeSql(b,[m])},r=0,x=k.a.length;r=|=|>|<)(.+)/i;if(0d?c:d:d}};function $g(a,b){this.a=b}z($g,Uf);$g.prototype.logger=G("ydn.db.sql.req.IndexedDb");$g.prototype.executeSql=function(a,b,c){if(c=Rg(b,c))throw new je(c);c=sb(b.a);if(1==c.length){var d=X(this.a,c[0]);if(!d)throw new ge(c[0]);var e=Tg(b);if(e)for(var f=0;fh.version){var k=h.setVersion(b.version);k.a=function(a){H(e.logger,"migrating from "+h.version+" to "+b.version+" failed.");d(null,a)};k.onsuccess=function(){c(h,k.transaction,!0)}}else b.version==h.version?Lc(e.logger,Cc,"database version "+h.version+" ready to go"):H(e.logger,"connected database version "+h.version+" is higher than requested version."),e.K(function(a){a=Ef(b,a,!1,!0);0g.version)c(NaN,new de("existing version "+g.version+" is larger than "+this.a.version));else{var h= +this.a.version;this.b=t(h)?h:g.version+1;for(h=0;hthis.d)throw Error("[goog.structs.SimplePool] Initial cannot be greater than max");for(var c=0;ca&&(b=" ");100>a&&(b=" ");10>a&&(b=" ");return b+a}function vh(a){a=Math.round(a);return String(100+a/1E3%60).substring(1,3)+"."+String(1E3+a%1E3).substring(1,4)}new qh;var wh=!cc||cc&&9<=nc,xh=cc&&!lc("9");!ec||lc("528");dc&&lc("1.9b")||cc&&lc("8")||bc&&lc("9.5")||ec&&lc("528");dc&&!lc("8")||cc&&lc("9");function yh(a){yh[" "](a);return a}yh[" "]=function(){};function zh(a,b){df.call(this,a?a.type:"");this.a=this.target=null;this.clientY=this.clientX=0;this.b=null;if(a){this.type=a.type;this.target=a.target||a.srcElement;this.a=b;var c=a.relatedTarget;if(c&&dc)try{yh(c.nodeName)}catch(d){}this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:a.pageY;this.b=a;a.defaultPrevented&&this.preventDefault()}}z(zh,df); +zh.prototype.preventDefault=function(){zh.r.preventDefault.call(this);var a=this.b;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,xh)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Ah="closure_listenable_"+(1E6*Math.random()|0),Bh=0;function Ch(a,b,c,d,e){this.ga=a;this.a=null;this.src=b;this.type=c;this.Fa=!!d;this.Pa=e;this.key=++Bh;this.oa=this.Ea=!1}function Dh(a){a.oa=!0;a.ga=null;a.a=null;a.src=null;a.Pa=null};function Eh(a){this.src=a;this.a={};this.b=0}Eh.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.a[f];a||(a=this.a[f]=[],this.b++);var g=Fh(a,b,d,e);-1c.keyCode||void 0!=c.returnValue)){a:{var f=!1;if(0==c.keyCode)try{c.keyCode=-1;break a}catch(g){f=!0}if(f||void 0==c.returnValue)c.returnValue=!0}c=[];for(f=d.a;f;f=f.parentNode)c.push(f);for(var f=a.type,h=c.length-1;0<=h;h--)d.a=c[h],e&=Uh(c[h],f,!0,d);for(h=0;h>>0);function Lh(a){return fa(a)?a:a[Wh]||(a[Wh]=function(b){return a.handleEvent(b)})};function Xh(){qd.call(this);this.Z=new Eh(this);this.b=this;this.a=null}z(Xh,qd);Xh.prototype[Ah]=!0;Xh.prototype.addEventListener=function(a,b,c,d){Kh(this,a,b,c,d)};Xh.prototype.removeEventListener=function(a,b,c,d){Sh(this,a,b,c,d)};Xh.prototype.$=function(){Xh.r.$.call(this);if(this.Z){var a=this.Z,b=0,c;for(c in a.a){for(var d=a.a[c],e=0;ee;e++){var f;if(f="_ROWID_"!=c[e]){f=b;var g=c[e];f=!(t(f.keyPath)&&(1== +f.e.length?f.keyPath===g:v(g)&&tb(f.e,g)))}if(f&&c[e]!=d)throw new C('select field name must be "'+d+'", but "'+c[e]+'" found.');}d=3}else throw new C("Selecting more than 2 field names is not supported, but "+c.length+" fields selected.");return new $(this.db,this.b,d,a)};l.jc=function(a){a=a||100;var b=4,c=oi(this);this.c&&this.c[0]&&(c=c.Ob(this.c[0],this.c[1]));if(2==this.type||3==this.type||1==this.type)b=this.type;a=cf(this.db,b,c,a);a.C(function(){"rest"==c.f&&(this.c=[c.g,c.h])},this);return a}; +l.nb=function(){return[this.a.clone()]};function oi(a){return hi(a.a,!(2==a.type||3==a.type||1==a.type))} +l.Yb=function(a,b){var c=oi(this);c.a&&(c=new N(c.c,c.d,c.b,c.V(),c.qa(),!1,c.e));if(1>arguments.length)throw new C("too few arguments");if(2==arguments.length){if(!w(a)&&u(a)){if(!u(b))throw new C("an array is expected for second argument but, "+Ke(b)+" of type "+typeof b+" found");if(a.length!=b.length)throw new C("length of two input arguments must be equal but, "+a.length+" and "+b.length+" found");}}else if(1==arguments.length){if(!y(a))throw new C("an object is expected but, "+Ke(a)+" of type "+ +typeof a+" found");}else throw new C("too many arguments");var d=this.db.open(function(c){var f=c.aa();if(w(a))$c(f,a,b);else if(u(a))for(var g=0;ga?"0"+a:String(a)}function ui(a,b){var c=(a.e-b)/1E3,d=c.toFixed(3),e=0;if(1>c)e=2;else for(;100>c;)e++,c*=10;for(;0',ua(Da(va(a.d))));this.b&&a.b&&c.push("
",ua(Da(a.a||"")));c.push("");c.push("
");return c.join("")};function wi(a){ri.call(this,a)}z(wi,ri); +wi.prototype.c=function(a){var b=[];b.push(this.d," ");this.a&&b.push("[",si(a),"] ");b.push("[",ui(a,this.f.get()),"s] ");b.push("[",a.c,"] ");b.push(a.d);this.b&&a.b&&b.push("\n",a.a);b.push("\n");return b.join("")};function xi(){this.f=ka(this.e,this);this.a=new wi;this.a.a=!1;this.b=this.a.b=!1;this.c="";this.d={}}xi.prototype.e=function(a){if(!this.d[a.c]){var b=this.a.c(a),c=yi;if(c)switch(a.f){case wc:zi(c,"info",b);break;case xc:zi(c,"error",b);break;case yc:zi(c,"warn",b);break;default:zi(c,"debug",b)}else window.opera?window.opera.postError(b):this.c+=b}};var yi=window.console;function zi(a,b,c){if(a[b])a[b](c);else a.log(c)};var Ai=!cc||cc&&9<=nc;!dc&&!cc||cc&&cc&&9<=nc||dc&&lc("1.9.1");cc&&lc("9");function Bi(a,b){Mb(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Ci?a.setAttribute(Ci[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})}var Ci={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"}; +function Di(a,b,c){function d(c){c&&b.appendChild(w(c)?a.createTextNode(c):c)}for(var e=2;e");f=f.join("")}f=d.createElement(f);g&&(w(g)?f.className=g:u(g)?f.className=g.join(" "):Bi(f,g));2=this.a.scrollHeight-this.a.scrollTop-this.a.clientHeight,c=this.d.createElement("div");c.className="logmsg";c.innerHTML=this.b.c(a);this.a.appendChild(c);b&&(this.a.scrollTop=this.a.scrollHeight)};Hi.prototype.clear=function(){this.a.innerHTML=""};var Ii=null,Ji=null;G("ydn.debug");na("ydn.debug.log",function(a,b,c){if(ea(b))b=new vc("log",b);else if(w(b)){b=b.toUpperCase();if(!Fc){Fc={};for(var d=0,e;e=Ec[d];d++)Fc[e.value]=e,Fc[e.name]=e}b=Fc[b]||null}else b=Bc;G(a||"ydn").b=b;t(c)?Ji||(a=Ji=new Hi(c),!0!=a.c&&(c=Kc(),b=a.f,c.a||(c.a=[]),c.a.push(b),a.c=!0),Kc().b=yc):Ii||Ji||(a=Ii=new xi,!0!=a.b&&(c=Kc(),b=a.f,c.a||(c.a=[]),c.a.push(b),a.b=!0),Kc().b=yc)});na("ydn.db.Storage",$h);})(); + + //# sourceMappingURL=ydn.db-dev.js.map + \ No newline at end of file diff --git a/src/classes/site.ts b/src/classes/site.ts index 549e4dcb9..9f380aa46 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -578,6 +578,8 @@ export class CoreSite { data.moodlewssettingfilter = preSets.filter === false ? false : true; data.moodlewssettingfileurl = preSets.rewriteurls === false ? false : true; + const originalData = data; + // Convert the values to string before starting the cache process. try { data = this.wsProvider.convertValuesToString(data, wsPreSets.cleanUnicode); @@ -586,7 +588,7 @@ export class CoreSite { return Promise.reject(this.utils.createFakeWSError('core.unicodenotsupportedcleanerror', true)); } - return this.getFromCache(method, data, preSets).catch(() => { + return this.getFromCache(method, data, preSets, false, originalData).catch(() => { // Do not pass those options to the core WS factory. return this.wsProvider.call(method, data, wsPreSets).then((response) => { if (preSets.saveToCache) { @@ -666,7 +668,7 @@ export class CoreSite { preSets.omitExpires = true; preSets.getFromCache = true; - return this.getFromCache(method, data, preSets, true).catch(() => { + return this.getFromCache(method, data, preSets, true, originalData).catch(() => { return Promise.reject(error); }); }); @@ -711,16 +713,29 @@ export class CoreSite { return Md5.hashAsciiStr(method + ':' + this.utils.sortAndStringify(data)); } + /** + * Get the cache ID used in Ionic 1 version of the app. + * + * @param {string} method The WebService method. + * @param {any} data Arguments to pass to the method. + * @return {string} Cache ID. + */ + protected getCacheOldId(method: string, data: any): string { + return Md5.hashAsciiStr(method + ':' + JSON.stringify(data)); + } + /** * Get a WS response from cache. * * @param {string} method The WebService method to be called. * @param {any} data Arguments to pass to the method. * @param {CoreSiteWSPreSets} preSets Extra options. - * @param {boolean} emergency Whether it's an "emergency" cache call (WS call failed). + * @param {boolean} [emergency] Whether it's an "emergency" cache call (WS call failed). + * @param {any} [originalData] Arguments to pass to the method before being converted to strings. * @return {Promise} Promise resolved with the WS response. */ - protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean): Promise { + protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean, originalData?: any) + : Promise { if (!this.db || !preSets.getFromCache) { return Promise.reject(null); } @@ -746,7 +761,17 @@ export class CoreSite { return entries[0]; }); } else { - promise = this.db.getRecord(this.WS_CACHE_TABLE, { id: id }); + promise = this.db.getRecord(this.WS_CACHE_TABLE, { id: id }).catch(() => { + // Entry not found, try to get it using the old ID. + const oldId = this.getCacheOldId(method, originalData || {}); + + return this.db.getRecord(this.WS_CACHE_TABLE, { id: oldId }).then((entry) => { + // Update the entry ID to use the new one. + this.db.updateRecords(this.WS_CACHE_TABLE, {id: id}, {id: oldId}); + + return entry; + }); + }); } return promise.then((entry) => { diff --git a/src/core/course/classes/module-prefetch-handler.ts b/src/core/course/classes/module-prefetch-handler.ts index 076b0e44e..a663354f4 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -457,7 +457,12 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref // Package marked as downloading, call the download function. // Send all the params except downloadFn. This includes all params passed after siteId. return downloadFn.apply(downloadFn, [module, courseId, single, siteId].concat(args)); - }).then((extra: string) => { + }).then((extra: any) => { + // Only accept string types. + if (typeof extra != 'string') { + extra = ''; + } + // Prefetch finished, mark as downloaded. return this.setDownloaded(module.id, siteId, extra); }).catch((error) => { diff --git a/src/core/emulator/classes/sqlitedb.ts b/src/core/emulator/classes/sqlitedb.ts index 90d5654d2..5aedfcfe3 100644 --- a/src/core/emulator/classes/sqlitedb.ts +++ b/src/core/emulator/classes/sqlitedb.ts @@ -132,7 +132,10 @@ export class SQLiteDBMock extends SQLiteDB { tx.executeSql(query, params, (tx, results) => { resolve(results); - }, reject); + }, (tx, error) => { + console.error(query, params, error); + reject(error); + }); })); }); diff --git a/src/core/emulator/emulator.module.ts b/src/core/emulator/emulator.module.ts index 93a7fdb5e..68260edc6 100644 --- a/src/core/emulator/emulator.module.ts +++ b/src/core/emulator/emulator.module.ts @@ -159,10 +159,11 @@ export const IONIC_NATIVE_PROVIDERS = [ Keyboard, { provide: LocalNotifications, - deps: [CoreAppProvider, CoreUtilsProvider], - useFactory: (appProvider: CoreAppProvider, utils: CoreUtilsProvider): LocalNotifications => { + deps: [CoreAppProvider, CoreUtilsProvider, CoreTextUtilsProvider], + useFactory: (appProvider: CoreAppProvider, utils: CoreUtilsProvider, txtUtils: CoreTextUtilsProvider) + : LocalNotifications => { // Use platform instead of CoreAppProvider to prevent circular dependencies. - return appProvider.isMobile() ? new LocalNotifications() : new LocalNotificationsMock(appProvider, utils); + return appProvider.isMobile() ? new LocalNotifications() : new LocalNotificationsMock(appProvider, utils, txtUtils); } }, { diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index 50f9ea52a..47ba21038 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications'; import { CoreAppProvider } from '@providers/app'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; @@ -49,6 +50,22 @@ export class LocalNotificationsMock extends LocalNotifications { type: 'INTEGER', primaryKey: true }, + { + name: 'title', + type: 'TEXT' + }, + { + name: 'text', + type: 'TEXT' + }, + { + name: 'at', + type: 'INTEGER' + }, + { + name: 'data', + type: 'TEXT' + }, { name: 'triggered', type: 'INTEGER' @@ -71,7 +88,7 @@ export class LocalNotificationsMock extends LocalNotifications { at: undefined }; - constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) { + constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider) { super(); this.appDB = appProvider.getDB(); @@ -281,7 +298,15 @@ export class LocalNotificationsMock extends LocalNotifications { * @return {Promise} Promise resolved with the notifications. */ protected getAllNotifications(): Promise { - return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE); + return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE).then((notifications) => { + notifications.forEach((notification) => { + notification.at = new Date(notification.at); + notification.data = this.textUtils.parseJSON(notification.data); + notification.triggered = !!notification.triggered; + }); + + return notifications; + }); } /** @@ -663,15 +688,22 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Store a notification in local DB. * - * @param {any} notification Notification to store. + * @param {ILocalNotification} notification Notification to store. * @param {boolean} triggered Whether the notification has been triggered. * @return {Promise} Promise resolved when stored. */ - protected storeNotification(notification: any, triggered: boolean): Promise { - notification = Object.assign({}, notification); // Clone the object. - notification.triggered = !!triggered; + protected storeNotification(notification: ILocalNotification, triggered: boolean): Promise { + // Only store some of the properties. + const entry = { + id : notification.id, + title: notification.title, + text: notification.text, + at: notification.at ? notification.at.getTime() : 0, + data: notification.data ? JSON.stringify(notification.data) : '{}', + triggered: triggered ? 1 : 0 + }; - return this.appDB.insertRecord(this.DESKTOP_NOTIFS_TABLE, notification); + return this.appDB.insertRecord(this.DESKTOP_NOTIFS_TABLE, entry); } /** diff --git a/src/core/question/providers/question.ts b/src/core/question/providers/question.ts index e7d81d630..36fc7fed9 100644 --- a/src/core/question/providers/question.ts +++ b/src/core/question/providers/question.ts @@ -73,7 +73,7 @@ export class CoreQuestionProvider { notNull: true }, { - name: 'attemptId', + name: 'attemptid', type: 'INTEGER', notNull: true }, @@ -83,11 +83,11 @@ export class CoreQuestionProvider { notNull: true }, { - name: 'componentId', + name: 'componentid', type: 'INTEGER' }, { - name: 'userId', + name: 'userid', type: 'INTEGER' }, { @@ -99,7 +99,7 @@ export class CoreQuestionProvider { type: 'TEXT' } ], - primaryKeys: ['component', 'attemptId', 'slot'] + primaryKeys: ['component', 'attemptid', 'slot'] }, { name: this.QUESTION_ANSWERS_TABLE, @@ -110,7 +110,7 @@ export class CoreQuestionProvider { notNull: true }, { - name: 'attemptId', + name: 'attemptid', type: 'INTEGER', notNull: true }, @@ -120,21 +120,17 @@ export class CoreQuestionProvider { notNull: true }, { - name: 'componentId', + name: 'componentid', type: 'INTEGER' }, { - name: 'userId', + name: 'userid', type: 'INTEGER' }, { - name: 'questionSlot', + name: 'questionslot', type: 'INTEGER' }, - { - name: 'state', - type: 'TEXT' - }, { name: 'value', type: 'TEXT' @@ -144,7 +140,7 @@ export class CoreQuestionProvider { type: 'INTEGER' } ], - primaryKeys: ['component', 'attemptId', 'name'] + primaryKeys: ['component', 'attemptid', 'name'] } ]; @@ -310,7 +306,7 @@ export class CoreQuestionProvider { */ getAnswer(component: string, attemptId: number, name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.QUESTION_ANSWERS_TABLE, {component, attemptId, name}); + return site.getDb().getRecord(this.QUESTION_ANSWERS_TABLE, {component: component, attemptid: attemptId, name: name}); }); } @@ -324,7 +320,7 @@ export class CoreQuestionProvider { */ getAttemptAnswers(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId}); + return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component: component, attemptid: attemptId}); }); } @@ -338,7 +334,7 @@ export class CoreQuestionProvider { */ getAttemptQuestions(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.QUESTION_TABLE, {component, attemptId}); + return site.getDb().getRecords(this.QUESTION_TABLE, {component: component, attemptid: attemptId}); }); } @@ -389,7 +385,7 @@ export class CoreQuestionProvider { */ getQuestion(component: string, attemptId: number, slot: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.QUESTION_TABLE, {component, attemptId, slot}); + return site.getDb().getRecord(this.QUESTION_TABLE, {component: component, attemptid: attemptId, slot: slot}); }); } @@ -405,8 +401,9 @@ export class CoreQuestionProvider { */ getQuestionAnswers(component: string, attemptId: number, slot: number, filter?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, questionSlot: slot}) - .then((answers) => { + return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component: component, attemptid: attemptId, + questionslot: slot}).then((answers) => { + if (filter) { // Get only answers that isn't "extra" data like sequencecheck or certainty. return this.getBasicAnswersFromArray(answers); @@ -467,7 +464,7 @@ export class CoreQuestionProvider { */ removeAttemptAnswers(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId}); + return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component: component, attemptid: attemptId}); }); } @@ -481,7 +478,7 @@ export class CoreQuestionProvider { */ removeAttemptQuestions(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.QUESTION_TABLE, {component, attemptId}); + return site.getDb().deleteRecords(this.QUESTION_TABLE, {component: component, attemptid: attemptId}); }); } @@ -496,7 +493,8 @@ export class CoreQuestionProvider { */ removeAnswer(component: string, attemptId: number, name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, name}); + return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component: component, attemptid: attemptId, + name: name}); }); } @@ -511,7 +509,7 @@ export class CoreQuestionProvider { */ removeQuestion(component: string, attemptId: number, slot: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.QUESTION_TABLE, {component, attemptId, slot}); + return site.getDb().deleteRecords(this.QUESTION_TABLE, {component: component, attemptid: attemptId, slot: slot}); }); } @@ -526,7 +524,8 @@ export class CoreQuestionProvider { */ removeQuestionAnswers(component: string, attemptId: number, slot: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, questionSlot: slot}); + return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component: component, attemptid: attemptId, + questionslot: slot}); }); } @@ -568,10 +567,10 @@ export class CoreQuestionProvider { const value = answers[name], entry = { component: component, - componentId: componentId, - attemptId: attemptId, - userId: userId, - questionSlot: this.getQuestionSlotFromName(name), + componentid: componentId, + attemptid: attemptId, + userid: userId, + questionslot: this.getQuestionSlotFromName(name), name: name, value: value, timemodified: timemodified @@ -602,7 +601,7 @@ export class CoreQuestionProvider { return this.sitesProvider.getSite(siteId).then((site) => { const entry = { component: component, - componentId: componentId, + componentid: componentId, attemptid: attemptId, userid: userId, number: question.number, diff --git a/src/core/settings/pages/general/general.ts b/src/core/settings/pages/general/general.ts index 27c7d1e7e..ab493b501 100644 --- a/src/core/settings/pages/general/general.ts +++ b/src/core/settings/pages/general/general.ts @@ -56,7 +56,7 @@ export class CoreSettingsGeneralPage { this.rteSupported = this.domUtils.isRichTextEditorSupported(); if (this.rteSupported) { this.configProvider.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true).then((richTextEditorEnabled) => { - this.richTextEditor = richTextEditorEnabled; + this.richTextEditor = !!richTextEditorEnabled; }); } @@ -81,7 +81,7 @@ export class CoreSettingsGeneralPage { * Called when the rich text editor is enabled or disabled. */ richTextEditorChanged(): void { - this.configProvider.set(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, this.richTextEditor); + this.configProvider.set(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, this.richTextEditor ? 1 : 0); } /** diff --git a/src/core/settings/pages/synchronization/synchronization.ts b/src/core/settings/pages/synchronization/synchronization.ts index 19be97a5d..48c7299ab 100644 --- a/src/core/settings/pages/synchronization/synchronization.ts +++ b/src/core/settings/pages/synchronization/synchronization.ts @@ -68,7 +68,7 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { }); this.configProvider.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true).then((syncOnlyOnWifi) => { - this.syncOnlyOnWifi = syncOnlyOnWifi; + this.syncOnlyOnWifi = !!syncOnlyOnWifi; }); } @@ -76,7 +76,7 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { * Called when sync only on wifi setting is enabled or disabled. */ syncOnlyOnWifiChanged(): void { - this.configProvider.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.syncOnlyOnWifi); + this.configProvider.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.syncOnlyOnWifi ? 1 : 0); } /** diff --git a/src/core/sharedfiles/providers/sharedfiles.ts b/src/core/sharedfiles/providers/sharedfiles.ts index 32e5fd716..1cf41d80f 100644 --- a/src/core/sharedfiles/providers/sharedfiles.ts +++ b/src/core/sharedfiles/providers/sharedfiles.ts @@ -31,7 +31,7 @@ export class CoreSharedFilesProvider { static SHARED_FILES_FOLDER = 'sharedfiles'; // Variables for the database. - protected SHARED_FILES_TABLE = 'wscache'; + protected SHARED_FILES_TABLE = 'shared_files'; protected tableSchema = { name: this.SHARED_FILES_TABLE, columns: [ diff --git a/src/index.html b/src/index.html index 2cef10c88..eeba3d3b6 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,8 @@ + + diff --git a/src/providers/config.ts b/src/providers/config.ts index c34166080..05e9a6a0e 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -77,10 +77,10 @@ export class CoreConfigProvider { * Set an app setting. * * @param {string} name The config name. - * @param {boolean|number|string} value The config value. Can only store primitive values, not objects. + * @param {number|string} value The config value. Can only store number or strings. * @return {Promise} Promise resolved when done. */ - set(name: string, value: boolean | number | string): Promise { + set(name: string, value: number | string): Promise { return this.appDB.insertRecord(this.TABLE_NAME, { name: name, value: value }); } } diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index b504a0ee8..6f1e3e902 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -20,11 +20,17 @@ import { CoreLoggerProvider } from './logger'; */ export interface CorePluginFileHandler { /** - * A name to identify the handler. It should match the "component" of pluginfile URLs. + * A name to identify the handler. * @type {string} */ name: string; + /** + * The "component" of the handler. It should match the "component" of pluginfile URLs. + * @type {string} + */ + component: string; + /** * Return the RegExp to match the revision on pluginfile URLs. * @@ -57,12 +63,12 @@ export class CorePluginFileDelegate { /** * Get the handler for a certain pluginfile url. * - * @param {string} pluginType Type of the plugin. + * @param {string} component Component of the plugin. * @return {CorePluginFileHandler} Handler. Undefined if no handler found for the plugin. */ - protected getPluginHandler(pluginType: string): CorePluginFileHandler { - if (typeof this.handlers[pluginType] != 'undefined') { - return this.handlers[pluginType]; + protected getPluginHandler(component: string): CorePluginFileHandler { + if (typeof this.handlers[component] != 'undefined') { + return this.handlers[component]; } } @@ -88,14 +94,14 @@ export class CorePluginFileDelegate { * @return {boolean} True if registered successfully, false otherwise. */ registerHandler(handler: CorePluginFileHandler): boolean { - if (typeof this.handlers[handler.name] !== 'undefined') { - this.logger.log(`Addon '${handler.name}' already registered`); + if (typeof this.handlers[handler.component] !== 'undefined') { + this.logger.log(`Handler '${handler.component}' already registered`); return false; } - this.logger.log(`Registered addon '${handler.name}'`); - this.handlers[handler.name] = handler; + this.logger.log(`Registered handler '${handler.component}'`); + this.handlers[handler.component] = handler; return true; } diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts index 659a069de..183d1b32b 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -13,13 +13,49 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreAppProvider } from './app'; import { CoreConfigProvider } from './config'; import { CoreFilepoolProvider } from './filepool'; import { CoreInitHandler, CoreInitDelegate } from './init'; import { CoreLocalNotificationsProvider } from './local-notifications'; import { CoreLoggerProvider } from './logger'; import { CoreSitesProvider } from './sites'; +import { CoreUtilsProvider } from './utils/utils'; import { CoreConfigConstants } from '../configconstants'; +import { SQLiteDB } from '@classes/sqlitedb'; + +/** + * Data to migrate a store of Ionic 1 app to the SQLite DB. + */ +export interface CoreUpdateManagerMigrateTable { + /** + * Current name of the store/table. + * @type {string} + */ + name: string; + + /** + * New name of the table. If not defined, "name" will be used. + * @type {string} + */ + newName?: string; + + /** + * An object to rename and convert some fields of the table/store. + * @type {object} + */ + fields?: { + name: string, // Field name in the old app. + newName?: string, // New field name. If not provided, keep the same name. + type?: string, // Type of the field if it needs to be treated: 'any', 'object', 'date', 'boolean'. + delete?: boolean // Whether the field must be deleted because it isn't needed in the new schema. + }[]; + + /** + * If set, all the fields that aren't in this array will be deleted. The names in this list should be the new names. + */ + filterFields?: string[]; +} /** * Factory to handle app updates. This factory shouldn't be used outside of core. @@ -36,8 +72,258 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { protected VERSION_APPLIED = 'version_applied'; protected logger; + /** + * Tables to migrate from app DB ('MoodleMobile'). Include all the core ones to decrease the dependencies. + * + * @type {CoreUpdateManagerMigrateTable[]} + */ + protected appDBTables: CoreUpdateManagerMigrateTable[] = [ + { + name: 'config', + newName: 'core_config', + fields: [ + { + name: 'value', + type: 'any' + } + ] + }, + { + name: 'cron' + }, + { + name: 'current_site', + fields: [ + { + name: 'siteid', + newName: 'siteId' + } + ] + }, + { + name: 'desktop_local_notifications', + fields: [ + { + name: 'at', + type: 'date' + }, + { + name: 'data', + type: 'object' + }, + { + name: 'triggered', + type: 'boolean' + } + ], + filterFields: ['id', 'title', 'text', 'at', 'data', 'triggered'] + }, + { + name: 'files_queue', + newName: 'filepool_files_queue', + fields: [ + { + name: 'isexternalfile', + type: 'boolean' + }, + { + name: 'links', + type: 'object' + }, + { + name: 'sortorder', + delete: true + } + ] + }, + { + name: 'notification_components' + }, + { + name: 'notification_sites' + }, + { + name: 'notifications_triggered' + }, + { + name: 'shared_files' + }, + { + name: 'sites', + fields: [ + { + name: 'siteurl', + newName: 'siteUrl' + }, + { + name: 'infos', + newName: 'info', + type: 'object' + }, + { + name: 'privatetoken', + newName: 'privateToken' + }, + { + name: 'config', + type: 'object' + }, + { + name: 'loggedout', + newName: 'loggedOut' + } + ] + }, + ]; + + /** + * Tables to migrate from each site DB. Include all the core ones to decrease the dependencies. + * + * @type {CoreUpdateManagerMigrateTable[]} + */ + protected siteDBTables: CoreUpdateManagerMigrateTable[] = [ + { + name: 'check_updates_times', + fields: [ + { + name: 'courseid', + newName: 'courseId' + } + ] + }, + { + name: 'course_status', + fields: [ + { + name: 'previous', + newName: 'previousStatus' + }, + { + name: 'downloadtime', + newName: 'downloadTime' + }, + { + name: 'previousdownloadtime', + newName: 'previousDownloadTime' + } + ] + }, + { + name: 'filepool', + newName: 'filepool_files', + fields: [ + { + name: 'stale', + type: 'boolean' + }, + { + name: 'downloaded', + newName: 'downloadTime' + }, + { + name: 'isexternalfile', + type: 'boolean' + } + ] + }, + { + name: 'files_links', + newName: 'filepool_files_links', + fields: [ + { + name: 'componentAndId', + delete: true + } + ] + }, + { + name: 'filepool_packages', + fields: [ + { + name: 'downloadtime', + newName: 'downloadTime' + }, + { + name: 'previousdownloadtime', + newName: 'previousDownloadTime' + }, + { + name: 'revision', // Move the value of 'revision' to 'extra' so SCORMs keep working. + newName: 'extra' + }, + { + name: 'timemodified', + delete: true + } + ] + }, + { + name: 'mm_emulator_last_received_notification', + newName: 'core_emulator_last_received_notification', + filterFields: ['component', 'id', 'timecreated'] + }, + { + name: 'questions', + fields: [ + { + name: 'componentId', + newName: 'componentid' + }, + { + name: 'componentAndAttempt', + delete: true + }, + { + name: 'componentAndComponentId', + delete: true + } + ] + }, + { + name: 'question_answers', + fields: [ + { + name: 'componentId', + newName: 'componentid' + }, + { + name: 'componentAndAttempt', + delete: true + }, + { + name: 'componentAndComponentId', + delete: true + }, + { + name: 'componentAndAttemptAndQuestion', + delete: true + } + ] + }, + { + name: 'sync' + }, + { + name: 'users' + }, + { + name: 'wscache', + fields: [ + { + name: 'data', + type: 'object' + }, + { + name: 'expirationtime', + newName: 'expirationTime' + } + ] + } + ]; + constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider, private sitesProvider: CoreSitesProvider, - private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider) { + private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider, + private utils: CoreUtilsProvider, private appProvider: CoreAppProvider) { this.logger = logger.getInstance('CoreUpdateManagerProvider'); } @@ -51,19 +337,28 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { const promises = [], versionCode = CoreConfigConstants.versioncode; - return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied: number) => { - // @todo: Migrate all data from ydn-db to SQLite if there is no versionApplied. + return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied) => { + if (!versionApplied) { + // No version applied, either the app was just installed or it's being updated from Ionic 1. + return this.migrateAllDBs().then(() => { + // DBs migrated, get the version applied again. + return this.configProvider.get(this.VERSION_APPLIED, 0); + }); + } else { + return versionApplied; + } + }).then((versionApplied: number) => { - if (versionCode >= 2013 && versionApplied < 2013) { + if (versionCode >= 2013 && versionApplied < 2013 && versionApplied > 0) { promises.push(this.migrateFileExtensions()); } - if (versionCode >= 2017 && versionApplied < 2017) { + if (versionCode >= 2017 && versionApplied < 2017 && versionApplied > 0) { promises.push(this.setCalendarDefaultNotifTime()); promises.push(this.setSitesConfig()); } - if (versionCode >= 2018 && versionApplied < 2018) { + if (versionCode >= 2018 && versionApplied < 2018 && versionApplied > 0) { promises.push(this.adaptForumOfflineStores()); } @@ -75,6 +370,174 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { }); } + /** + * Register several app tables to be migrated to the new schema. + * + * @param {CoreUpdateManagerMigrateTable[]} tables The tables to migrate. + */ + registerAppTablesMigration(tables: CoreUpdateManagerMigrateTable[]): void { + tables.forEach((table) => { + this.registerAppTableMigration(table); + }); + } + + /** + * Register an app table to be migrated to the new schema. + * + * @param {CoreUpdateManagerMigrateTable} table The table to migrate. + */ + registerAppTableMigration(table: CoreUpdateManagerMigrateTable): void { + this.appDBTables.push(table); + } + + /** + * Register several site tables to be migrated to the new schema. + * + * @param {CoreUpdateManagerMigrateTable[]} tables The tables to migrate. + */ + registerSiteTablesMigration(tables: CoreUpdateManagerMigrateTable[]): void { + tables.forEach((table) => { + this.registerSiteTableMigration(table); + }); + } + + /** + * Register a site table to be migrated to the new schema. + * + * @param {CoreUpdateManagerMigrateTable} table The table to migrate. + */ + registerSiteTableMigration(table: CoreUpdateManagerMigrateTable): void { + this.siteDBTables.push(table); + } + + /** + * Migrate all DBs and tables from the old format to SQLite. + * + * @return {Promise} Promise resolved when done. + */ + protected migrateAllDBs(): Promise { + if (!( window).ydn) { + // The ydn-db library is not loaded, stop. + return Promise.resolve(); + } + + // First migrate the app DB. + return this.migrateAppDB().then(() => { + // Now migrate all site DBs. + return this.sitesProvider.getSitesIds(); + }).then((ids) => { + const promises = []; + + ids.forEach((id) => { + promises.push(this.migrateSiteDB(id)); + }); + + return this.utils.allPromises(promises); + }); + } + + /** + * Migrate the app DB. + * + * @return {Promise} Promise resolved when done. + */ + protected migrateAppDB(): Promise { + const oldDb = new ( window).ydn.db.Storage('MoodleMobile'), + newDb = this.appProvider.getDB(); + + return this.migrateDB(oldDb, newDb, this.appDBTables); + } + + /** + * Migrate the DB of a certain site. + * + * @param {string} siteId The site ID. + * @return {Promise} Promise resolved when done. + */ + protected migrateSiteDB(siteId: string): Promise { + // Get the site DB. + return this.sitesProvider.getSiteDb(siteId).then((newDb) => { + const oldDb = new ( window).ydn.db.Storage('Site-' + siteId); + + return this.migrateDB(oldDb, newDb, this.siteDBTables); + }); + } + + /** + * Migrate all the tables of a certain DB to the SQLite DB. + * + * @param {any} oldDb The old DB (created using ydn-db). + * @param {SQLiteDB} newDb The new DB. + * @param {CoreUpdateManagerMigrateTable[]} tables The tables to migrate. + * @return {Promise} Promise resolved when done. + */ + protected migrateDB(oldDb: any, newDb: SQLiteDB, tables: CoreUpdateManagerMigrateTable[]): Promise { + if (!oldDb || !newDb) { + // Some of the DBs doesn't exist, stop. + return Promise.resolve(); + } + + const promises = []; + + tables.forEach((table) => { + + // Get current values. + promises.push(Promise.resolve(oldDb.values(table.name, undefined, 99999999)).then((entries) => { + const fields = table.fields || [], + filterFields = table.filterFields || []; + + // Treat the entries. + for (let i = 0; i < entries.length; i++) { + const entry = entries[i]; + + // Convert and rename the fields to match the new schema. + fields.forEach((field) => { + const value = entry[field.name]; + + // Convert the field to the right format. + if (field.type == 'object' || (field.type == 'any' && typeof value == 'object')) { + entry[field.name] = JSON.stringify(value); + } else if (field.type == 'date' && value) { + entry[field.name] = value.getTime(); + } else if (field.type == 'boolean' || (field.type == 'any' && typeof value == 'boolean')) { + entry[field.name] = value ? 1 : 0; + } + + if (field.newName) { + // Rename the field. + entry[field.newName] = entry[field.name]; + delete entry[field.name]; + } + + if (field.delete) { + // Delete the field. + delete entry[field.name]; + } + }); + + // Remove invalid and unneeded properties. + for (const name in entry) { + if (name.indexOf('$') === 0) { + // Property not valid, remove. + delete entry[name]; + + } else if (filterFields.length && filterFields.indexOf(name) == -1) { + // The property isn't present in filterFields, remove it. + delete entry[name]; + } + } + } + + // Now store the entries in the new DB. + return newDb.insertRecords(table.newName || table.name, entries); + }).catch((error) => { + this.logger.error('Error migrating table ' + table.name + ' to ' + (table.newName || table.name) + ': ', error); + })); + }); + + return this.utils.allPromises(promises); + } + /** * Migrates files filling extensions. * diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 510c7876d..a7b961e9b 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -548,7 +548,9 @@ export class CoreDomUtilsProvider { */ isRichTextEditorEnabled(): Promise { if (this.isRichTextEditorSupported()) { - return this.configProvider.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true); + return this.configProvider.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true).then((enabled) => { + return !!enabled; + }); } return Promise.resolve(false);