forked from EVOgeek/Vmeda.Online
		
	MOBILE-4304 scorm: Update database usage
This commit is contained in:
		
							parent
							
								
									38d0ad1aad
								
							
						
					
					
						commit
						b6f32dfddd
					
				| @ -13,7 +13,6 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { SQLiteDB } from '@classes/sqlitedb'; | ||||
| import { CoreUser } from '@features/user/services/user'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreSync } from '@services/sync'; | ||||
| @ -38,6 +37,10 @@ import { | ||||
|     AddonModScormUserDataMap, | ||||
|     AddonModScormWSSco, | ||||
| } from './scorm'; | ||||
| import { lazyMap, LazyMap } from '@/core/utils/lazy-map'; | ||||
| import { asyncInstance, AsyncInstance } from '@/core/utils/async-instance'; | ||||
| import { CoreDatabaseTable } from '@classes/database/database-table'; | ||||
| import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle offline SCORM. | ||||
| @ -47,8 +50,36 @@ export class AddonModScormOfflineProvider { | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     protected tracksTables: LazyMap< | ||||
|         AsyncInstance<CoreDatabaseTable<AddonModScormTrackDBRecord, 'scormid' | 'userid' | 'attempt' | 'scoid' | 'element'>> | ||||
|     >; | ||||
| 
 | ||||
|     protected attemptsTables: LazyMap< | ||||
|         AsyncInstance<CoreDatabaseTable<AddonModScormAttemptDBRecord, 'scormid' | 'userid' | 'attempt'>> | ||||
|     >; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logger = CoreLogger.getInstance('AddonModScormOfflineProvider'); | ||||
|         this.tracksTables = lazyMap( | ||||
|             siteId => asyncInstance( | ||||
|                 () => CoreSites.getSiteTable(TRACKS_TABLE_NAME, { | ||||
|                     siteId, | ||||
|                     primaryKeyColumns: ['scormid', 'userid', 'attempt', 'scoid', 'element'], | ||||
|                     config: { cachingStrategy: CoreDatabaseCachingStrategy.None }, | ||||
|                     onDestroy: () => delete this.tracksTables[siteId], | ||||
|                 }), | ||||
|             ), | ||||
|         ); | ||||
|         this.attemptsTables = lazyMap( | ||||
|             siteId => asyncInstance( | ||||
|                 () => CoreSites.getSiteTable(ATTEMPTS_TABLE_NAME, { | ||||
|                     siteId, | ||||
|                     primaryKeyColumns: ['scormid', 'userid', 'attempt'], | ||||
|                     config: { cachingStrategy: CoreDatabaseCachingStrategy.None }, | ||||
|                     onDestroy: () => delete this.tracksTables[siteId], | ||||
|                 }), | ||||
|             ), | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -76,40 +107,43 @@ 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 currentAttemptConditions: AddonModScormOfflineDBCommonData = { | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|             attempt, | ||||
|         }; | ||||
|         const newAttemptConditions: AddonModScormOfflineDBCommonData = { | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|             attempt: newAttempt, | ||||
|         }; | ||||
|         const newAttemptData: Partial<AddonModScormAttemptDBRecord> = { | ||||
|             attempt: newAttempt, | ||||
|             timemodified: CoreTimeUtils.timestamp(), | ||||
|         }; | ||||
| 
 | ||||
|         // Block the SCORM so it can't be synced.
 | ||||
|         CoreSync.blockOperation(AddonModScormProvider.COMPONENT, scormId, 'changeAttemptNumber', site.id); | ||||
| 
 | ||||
|         try { | ||||
|             await db.updateRecords(ATTEMPTS_TABLE_NAME, newAttemptData, currentAttemptConditions); | ||||
|             const currentAttemptConditions = { | ||||
|                 sql: 'scormid = ? AND userid = ? AND attempt = ?', | ||||
|                 sqlParams: [scormId, userId, attempt], | ||||
|                 js: (record: AddonModScormOfflineDBCommonData) => | ||||
|                     record.scormid === scormId && | ||||
|                     record.userid === userId && | ||||
|                     record.attempt === attempt, | ||||
|             }; | ||||
| 
 | ||||
|             await this.attemptsTables[site.id].updateWhere( | ||||
|                 { attempt: newAttempt, timemodified: CoreTimeUtils.timestamp() }, | ||||
|                 currentAttemptConditions, | ||||
|             ); | ||||
| 
 | ||||
|             try { | ||||
|                 // Now update the attempt number of all the tracks and mark them as not synced.
 | ||||
|                 const newTrackData: Partial<AddonModScormTrackDBRecord> = { | ||||
|                     attempt: newAttempt, | ||||
|                     synced: 0, | ||||
|                 }; | ||||
| 
 | ||||
|                 await db.updateRecords(TRACKS_TABLE_NAME, newTrackData, currentAttemptConditions); | ||||
|                 await this.tracksTables[site.id].updateWhere( | ||||
|                     { attempt: newAttempt, synced: 0 }, | ||||
|                     currentAttemptConditions, | ||||
|                 ); | ||||
|             } catch (error) { | ||||
|                 // Failed to update the tracks, restore the old attempt number.
 | ||||
|                 await db.updateRecords(ATTEMPTS_TABLE_NAME, { attempt }, newAttemptConditions); | ||||
|                 await this.attemptsTables[site.id].updateWhere( | ||||
|                     { attempt }, | ||||
|                     { | ||||
|                         sql: 'scormid = ? AND userid = ? AND attempt = ?', | ||||
|                         sqlParams: [scormId, userId, newAttempt], | ||||
|                         js: (attempt) => | ||||
|                             attempt.scormid === scormId && | ||||
|                             attempt.userid === userId && | ||||
|                             attempt.attempt === newAttempt, | ||||
|                     }, | ||||
|                 ); | ||||
| 
 | ||||
|                 throw error; | ||||
|             } | ||||
| @ -148,7 +182,6 @@ export class AddonModScormOfflineProvider { | ||||
|         CoreSync.blockOperation(AddonModScormProvider.COMPONENT, scorm.id, 'createNewAttempt', site.id); | ||||
| 
 | ||||
|         // Create attempt in DB.
 | ||||
|         const db = site.getDb(); | ||||
|         const entry: AddonModScormAttemptDBRecord = { | ||||
|             scormid: scorm.id, | ||||
|             userid: userId, | ||||
| @ -166,7 +199,7 @@ export class AddonModScormOfflineProvider { | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await db.insertRecord(ATTEMPTS_TABLE_NAME, entry); | ||||
|             await this.attemptsTables[site.id].insert(entry); | ||||
| 
 | ||||
|             // Store all the data in userData.
 | ||||
|             const promises: Promise<void>[] = []; | ||||
| @ -204,16 +237,15 @@ export class AddonModScormOfflineProvider { | ||||
| 
 | ||||
|         this.logger.debug(`Delete offline attempt ${attempt} in SCORM ${scormId}`); | ||||
| 
 | ||||
|         const db = site.getDb(); | ||||
|         const conditions: AddonModScormOfflineDBCommonData = { | ||||
|         const conditions = { | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|             attempt, | ||||
|         }; | ||||
| 
 | ||||
|         await Promise.all([ | ||||
|             db.deleteRecords(ATTEMPTS_TABLE_NAME, conditions), | ||||
|             db.deleteRecords(TRACKS_TABLE_NAME, conditions), | ||||
|             this.attemptsTables[site.id].delete(conditions), | ||||
|             this.tracksTables[site.id].delete(conditions), | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| @ -280,9 +312,9 @@ export class AddonModScormOfflineProvider { | ||||
|      * @returns Promise resolved when the offline attempts are retrieved. | ||||
|      */ | ||||
|     async getAllAttempts(siteId?: string): Promise<AddonModScormOfflineAttempt[]> { | ||||
|         const db = await CoreSites.getSiteDb(siteId); | ||||
|         siteId ??= CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const attempts = await db.getAllRecords<AddonModScormAttemptDBRecord>(ATTEMPTS_TABLE_NAME); | ||||
|         const attempts = await this.attemptsTables[siteId].getMany(); | ||||
| 
 | ||||
|         return attempts.map((attempt) => this.parseAttempt(attempt)); | ||||
|     } | ||||
| @ -300,7 +332,7 @@ export class AddonModScormOfflineProvider { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         userId = userId || site.getUserId(); | ||||
| 
 | ||||
|         const attemptRecord = await site.getDb().getRecord<AddonModScormAttemptDBRecord>(ATTEMPTS_TABLE_NAME, { | ||||
|         const attemptRecord = await this.attemptsTables[site.id].getOneByPrimaryKey({ | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|             attempt, | ||||
| @ -340,7 +372,7 @@ export class AddonModScormOfflineProvider { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         userId = userId || site.getUserId(); | ||||
| 
 | ||||
|         const attempts = await site.getDb().getRecords<AddonModScormAttemptDBRecord>(ATTEMPTS_TABLE_NAME, { | ||||
|         const attempts = await this.attemptsTables[site.id].getMany({ | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|         }); | ||||
| @ -428,7 +460,7 @@ export class AddonModScormOfflineProvider { | ||||
|             conditions.synced = 1; | ||||
|         } | ||||
| 
 | ||||
|         const tracks = await site.getDb().getRecords<AddonModScormTrackDBRecord>(TRACKS_TABLE_NAME, conditions); | ||||
|         const tracks = await this.tracksTables[site.id].getMany(conditions); | ||||
| 
 | ||||
|         return this.parseTracks(tracks); | ||||
|     } | ||||
| @ -598,7 +630,6 @@ export class AddonModScormOfflineProvider { | ||||
|         userId = userId || site.getUserId(); | ||||
| 
 | ||||
|         const scoUserData = scoData?.userdata || {}; | ||||
|         const db = site.getDb(); | ||||
|         let lessonStatusInserted = false; | ||||
| 
 | ||||
|         if (forceCompleted) { | ||||
| @ -611,7 +642,16 @@ export class AddonModScormOfflineProvider { | ||||
|                 if (scoUserData['cmi.core.lesson_status'] == 'incomplete') { | ||||
|                     lessonStatusInserted = true; | ||||
| 
 | ||||
|                     await this.insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'completed'); | ||||
|                     await this.tracksTables[site.id].insert({ | ||||
|                         userid: userId, | ||||
|                         scormid: scormId, | ||||
|                         scoid: scoId, | ||||
|                         attempt, | ||||
|                         element: 'cmi.core.lesson_status', | ||||
|                         value: JSON.stringify('completed'), | ||||
|                         timemodified: CoreTimeUtils.timestamp(), | ||||
|                         synced: 0, | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -622,81 +662,35 @@ export class AddonModScormOfflineProvider { | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await this.insertTrackToDB(db, userId, scormId, scoId, attempt, element, value); | ||||
|             await this.tracksTables[site.id].insert({ | ||||
|                 userid: userId, | ||||
|                 scormid: scormId, | ||||
|                 scoid: scoId, | ||||
|                 attempt, | ||||
|                 element, | ||||
|                 value: value === undefined ? null : JSON.stringify(value), | ||||
|                 timemodified: CoreTimeUtils.timestamp(), | ||||
|                 synced: 0, | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             if (lessonStatusInserted) { | ||||
|                 // Rollback previous insert.
 | ||||
|                 await this.insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'incomplete'); | ||||
|                 await this.tracksTables[site.id].insert({ | ||||
|                     userid: userId, | ||||
|                     scormid: scormId, | ||||
|                     scoid: scoId, | ||||
|                     attempt, | ||||
|                     element: 'cmi.core.lesson_status', | ||||
|                     value: JSON.stringify('incomplete'), | ||||
|                     timemodified: CoreTimeUtils.timestamp(), | ||||
|                     synced: 0, | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Insert a track in the DB. | ||||
|      * | ||||
|      * @param db Site's DB. | ||||
|      * @param userId User ID. | ||||
|      * @param scormId SCORM ID. | ||||
|      * @param scoId SCO ID. | ||||
|      * @param attempt Attempt number. | ||||
|      * @param element Name of the element to insert. | ||||
|      * @param value Value of the element to insert. | ||||
|      * @param synchronous True if insert should NOT return a promise. Please use it only if synchronous is a must. | ||||
|      * @returns Returns a promise if synchronous=false, otherwise returns a boolean. | ||||
|      */ | ||||
|     protected insertTrackToDB( | ||||
|         db: SQLiteDB, | ||||
|         userId: number, | ||||
|         scormId: number, | ||||
|         scoId: number, | ||||
|         attempt: number, | ||||
|         element: string, | ||||
|         value: AddonModScormDataValue | undefined, | ||||
|         synchronous: true, | ||||
|     ): boolean; | ||||
|     protected insertTrackToDB( | ||||
|         db: SQLiteDB, | ||||
|         userId: number, | ||||
|         scormId: number, | ||||
|         scoId: number, | ||||
|         attempt: number, | ||||
|         element: string, | ||||
|         value?: AddonModScormDataValue, | ||||
|         synchronous?: false, | ||||
|     ): Promise<number>; | ||||
|     protected insertTrackToDB( | ||||
|         db: SQLiteDB, | ||||
|         userId: number, | ||||
|         scormId: number, | ||||
|         scoId: number, | ||||
|         attempt: number, | ||||
|         element: string, | ||||
|         value?: AddonModScormDataValue, | ||||
|         synchronous?: boolean, | ||||
|     ): boolean | Promise<number> { | ||||
|         const entry: AddonModScormTrackDBRecord = { | ||||
|             userid: userId, | ||||
|             scormid: scormId, | ||||
|             scoid: scoId, | ||||
|             attempt, | ||||
|             element: element, | ||||
|             value: value === undefined ? null : JSON.stringify(value), | ||||
|             timemodified: CoreTimeUtils.timestamp(), | ||||
|             synced: 0, | ||||
|         }; | ||||
| 
 | ||||
|         if (synchronous) { | ||||
|             // The insert operation is always asynchronous, always return true.
 | ||||
|             db.insertRecord(TRACKS_TABLE_NAME, entry); | ||||
| 
 | ||||
|             return true; | ||||
|         } else { | ||||
|             return db.insertRecord(TRACKS_TABLE_NAME, entry); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Insert a track in the offline tracks store, returning a synchronous value. | ||||
|      * Please use this function only if synchronous is a must. It's recommended to use insertTrack. | ||||
| @ -730,8 +724,7 @@ export class AddonModScormOfflineProvider { | ||||
|         } | ||||
| 
 | ||||
|         const scoUserData = scoData?.userdata || {}; | ||||
|         const db = CoreSites.getRequiredCurrentSite().getDb(); | ||||
|         let lessonStatusInserted = false; | ||||
|         const siteId = CoreSites.getRequiredCurrentSite().id; | ||||
| 
 | ||||
|         if (forceCompleted) { | ||||
|             if (element == 'cmi.core.lesson_status' && value == 'incomplete') { | ||||
| @ -741,11 +734,16 @@ export class AddonModScormOfflineProvider { | ||||
|             } | ||||
|             if (element == 'cmi.core.score.raw') { | ||||
|                 if (scoUserData['cmi.core.lesson_status'] == 'incomplete') { | ||||
|                     lessonStatusInserted = true; | ||||
| 
 | ||||
|                     if (!this.insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'completed', true)) { | ||||
|                         return false; | ||||
|                     } | ||||
|                     this.tracksTables[siteId].syncInsert({ | ||||
|                         userid: userId, | ||||
|                         scormid: scormId, | ||||
|                         scoid: scoId, | ||||
|                         attempt, | ||||
|                         element: 'cmi.core.lesson_status', | ||||
|                         value: JSON.stringify('completed'), | ||||
|                         timemodified: CoreTimeUtils.timestamp(), | ||||
|                         synced: 0, | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -755,15 +753,16 @@ export class AddonModScormOfflineProvider { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (!this.insertTrackToDB(db, userId, scormId, scoId, attempt, element, value, true)) { | ||||
|             // Insert failed.
 | ||||
|             if (lessonStatusInserted) { | ||||
|                 // Rollback previous insert.
 | ||||
|                 this.insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'incomplete', true); | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
|         this.tracksTables[siteId].syncInsert({ | ||||
|             userid: userId, | ||||
|             scormid: scormId, | ||||
|             scoid: scoId, | ||||
|             attempt, | ||||
|             element: element, | ||||
|             value: value === undefined ? null : JSON.stringify(value), | ||||
|             timemodified: CoreTimeUtils.timestamp(), | ||||
|             synced: 0, | ||||
|         }); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| @ -784,7 +783,7 @@ export class AddonModScormOfflineProvider { | ||||
| 
 | ||||
|         this.logger.debug(`Mark SCO ${scoId} as synced for attempt ${attempt} in SCORM ${scormId}`); | ||||
| 
 | ||||
|         await site.getDb().updateRecords(TRACKS_TABLE_NAME, { synced: 1 }, <Partial<AddonModScormTrackDBRecord>> { | ||||
|         await this.tracksTables[site.id].update({ synced: 1 }, { | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|             attempt, | ||||
| @ -971,10 +970,13 @@ export class AddonModScormOfflineProvider { | ||||
|             snapshot: JSON.stringify(this.removeDefaultData(userData)), | ||||
|         }; | ||||
| 
 | ||||
|         await site.getDb().updateRecords(ATTEMPTS_TABLE_NAME, newData, <Partial<AddonModScormAttemptDBRecord>> { | ||||
|             scormid: scormId, | ||||
|             userid: userId, | ||||
|             attempt, | ||||
|         await this.attemptsTables[site.id].updateWhere(newData, { | ||||
|             sql: 'scormid = ? AND userid = ? AND attempt = ?', | ||||
|             sqlParams: [scormId, userId, attempt], | ||||
|             js: (record: AddonModScormOfflineDBCommonData) => | ||||
|                 record.scormid === scormId && | ||||
|                 record.userid === userId && | ||||
|                 record.attempt === attempt, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -258,6 +258,18 @@ export class CoreDatabaseTable< | ||||
|         await this.database.insertRecord(this.tableName, record); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Insert a new record synchronously. | ||||
|      * | ||||
|      * @param record Database record. | ||||
|      */ | ||||
|     syncInsert(record: DBRecord): void { | ||||
|         // The current database architecture does not support synchronous operations,
 | ||||
|         // so calling this method will mean that errors will be silenced. Because of that,
 | ||||
|         // this should only be called if using the asynchronous alternatives is not possible.
 | ||||
|         this.insert(record); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update records matching the given conditions. | ||||
|      * | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user