import * as _ from "lodash" ; import * as Q from "q" ; import { ActivitySourceDataModel , AnalysisDataModel , SyncedActivityModel , UserSettingsModel } from "@elevate/shared/models" ; import { AppResourcesModel } from "../models/app-resources.model" ; import { ComputeActivityThreadMessageModel } from "../models/compute-activity-thread-message.model" ; import { StreamActivityModel } from "../models/sync/stream-activity.model" ; import { SyncNotifyModel } from "../models/sync/sync-notify.model" ; import { AthleteSnapshotResolver } from "@elevate/shared/resolvers" ; const ComputeAnalysisWorker = require ( "worker-loader?inline!./workers/compute-analysis.worker" ) ; export class MultipleActivityProcessor { constructor ( appResources , userSettings , athleteModelResolver ) { this . appResources = appResources ; this . userSettings = userSettings ; this . athleteModelResolver = athleteModelResolver ; } public static outputFields : string [ ] = [ "id" , "name" , "type" , "display_type" , "private" , "bike_id" , "start_time" , "distance_raw" , "short_unit" , "moving_time_raw" , "elapsed_time_raw" , "trainer" , "commute" , "elevation_unit" , "elevation_gain_raw" , "calories" , "hasPowerMeter" ] ; protected appResources : AppResourcesModel ; protected userSettings : UserSettingsModel ; protected athleteModelResolver : AthleteSnapshotResolver ; return public compute ( activitiesWithStream ) { const deferred = Q . defer < SyncedActivityModel [ ] > ( ) ; let syncedActivitiesPercentageCount = 0 ; let activitiesComputedResults = [ ] ; const queue = activitiesWithStream . reduce ( ( promise , activityWithStream , index ) => { return promise . then ( ( ) => { activityWithStream . athleteSnapshot = this . athleteModelResolver . resolve ( new Date ( activityWithStream . start_time ) ) ; return this . computeActivity ( activityWithStream ) . then ( ( activityComputed ) => { activitiesComputedResults . push ( activityComputed ) ; const notify = { step : "syncedActivitiesPercentage" , progress : syncedActivitiesPercentageCount / activitiesWithStream . length * 100 , index : index , activityId : activityWithStream . id , } ; deferred . notify ( notify ) ; syncedActivitiesPercentageCount ++ ; } ) ; } ) ; } , Q . resolve ( { } ) ) ; queue . then ( ( ) => { if ( activitiesComputedResults . length !== activitiesWithStream . length ) { const errMessage = "activitiesComputedResults length mismatch with activitiesWithStream length: " + activitiesComputedResults . length + " != " + activitiesWithStream . length + ")" ; deferred . reject ( errMessage ) ; } else { let activitiesComputed = [ ] ; _ . forEach ( activitiesComputedResults , ( computedResult , index ) => { const activityComputed = _ . pick ( activitiesWithStream [ index ] , MultipleActivityProcessor . outputFields ) as SyncedActivityModel ; activityComputed . extendedStats = computedResult ; activityComputed . athleteSnapshot = activitiesWithStream [ index ] . athleteSnapshot ; activitiesComputed . push ( activityComputed ) ; } ) ; activitiesComputed = _ . sortBy ( activitiesComputed , ( item ) => { return ( new Date ( item . start_time ) ) . getTime ( ) ; } ) ; const notify = { step : "syncedActivitiesPercentage" , progress : 100 , } ; deferred . notify ( notify ) ; deferred . resolve ( activitiesComputed ) ; activitiesComputedResults = null ; activitiesWithStream = null ; activitiesComputed = null ; } } ) . catch ( ( error ) => { console . error ( error ) ; deferred . reject ( error ) ; } ) ; return deferred . promise ; } protected provideActivitySourceData ( activityWithStream ) { return { elevation : parseInt ( activityWithStream . elevation_gain ) , movingTime : activityWithStream . moving_time_raw , distance : activityWithStream . distance_raw } ; } protected computeActivity ( activityWithStream ) { const deferred = Q . defer < AnalysisDataModel > ( ) ; const computeAnalysisThread = new ComputeAnalysisWorker ( ) ; const activitySourceData = this . provideActivitySourceData ( activityWithStream ) ; const threadMessage = { activityType : activityWithStream . type , supportsGap : ( activityWithStream . type === "Run" ) , isTrainer : activityWithStream . trainer , appResources : this . appResources , userSettings : this . userSettings , isOwner : true , athleteSnapshot : activityWithStream . athleteSnapshot , hasPowerMeter : activityWithStream . hasPowerMeter , activitySourceData : activitySourceData , activityStream : activityWithStream . stream , bounds : null , returnZones : false } ; computeAnalysisThread . postMessage ( threadMessage ) ; computeAnalysisThread . onmessage = ( messageFromThread ) => { deferred . notify ( activityWithStream . id ) ; deferred . resolve ( messageFromThread . data ) ; computeAnalysisThread . terminate ( ) ; } ; computeAnalysisThread . onerror = ( err ) => { const errorMessage = { errObject : err , activityId : activityWithStream . id , } ; console . error ( errorMessage ) ; deferred . reject ( errorMessage ) ; computeAnalysisThread . terminate ( ) ; } ; return deferred . promise ; } }