initial commit
[CPE_learningsite] / CPE / CPE.App / CPE.App.Web / static / services / engagement / service.asmx.cs
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Configuration;
5 using System.Diagnostics;
6 using System.Linq;
7 using System.Threading;
8 using System.Web;
9 using System.Web.Services;
10 using CPE.App.Web.Code;
11 using CPE.App.Web.Models;
12
13
14 /*
15
16     Tyler Allen - 08/22/2016
17     Change notes:
18     I have commented out the section that was executing the thread within the application pool process
19     The new method calls a console application with the session variables
20     This will run outside the application pool
21
22 */
23
24 namespace CPE.App.Web.Static.services.engagement {
25     /// <summary>
26     ///     Summary description for engagement
27     /// </summary>
28     [WebService(Namespace = "http://cpeengagement.com/static/services/engagement/service/")]
29     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
30     [ToolboxItem(false)]
31     // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
32     // [System.Web.Script.Services.ScriptService]
33     public class service : BaseWebService {
34
35         private string notifyFilePath {
36             get { return ConfigurationManager.AppSettings["NotifyFilePath"]; }
37         }
38
39
40         [WebMethod]
41         public Setting GetMeetingSettings(int sco_id) {
42             Extensions.LogServiceCall("[service][GetMeetingSettings]", string.Format("Parameters: sco_id = {0}", sco_id));
43             var result = new Setting {
44                 Interval = 15,
45                 Text = "Respond",
46                 Duration = 1
47             };
48             var meetingSetting = Database.MeetingSettings.SingleOrDefault(ms => ms.MeetingKey == sco_id);
49             if(meetingSetting != null) {
50                 result.Interval = meetingSetting.Interval.HasValue
51                                       ? meetingSetting.Interval.Value
52                                       : 15;
53                 result.Text = meetingSetting.Text;
54                 result.Duration = meetingSetting.Duration.HasValue
55                                       ? meetingSetting.Duration.Value
56                                       : 1;
57             }
58             return result;
59         }
60
61         [WebMethod]
62         public ServiceStatus SetMeetingSettings(int sco_id, int interval, string text, int duration) {
63             Extensions.LogServiceCall("SetMeetingSettings", string.Format("Parameters: sco_id = {0} interval = {1} text = {2} duration = {3}", sco_id, interval, text, duration));
64             var result = new ServiceStatus {
65                 Success = true,
66                 Message = ""
67             };
68             try {
69                 var context = Database;
70                 var existing = context.Meetings.SingleOrDefault(m => m.SCO_ID == sco_id);
71                 if(existing != null) {
72                     if(existing.MeetingSetting == null) {
73                         var meetingSetting = new MeetingSetting {
74                             MeetingKey = sco_id,
75                             Interval = interval,
76                             Text = text,
77                             Duration = duration
78                         };
79                         context.MeetingSettings.InsertOnSubmit(meetingSetting);
80                     } else {
81                         existing.MeetingSetting.Interval = interval;
82                         existing.MeetingSetting.Text = text;
83                         existing.MeetingSetting.Duration = duration;
84                     }
85                 } else {
86                     var meeting = new Meeting {
87                         SCO_ID = sco_id,
88                         MeetingKey = sco_id
89                     };
90                     var meetingSetting = new MeetingSetting();
91                     meetingSetting.Interval = interval;
92                     meetingSetting.Text = text;
93                     meetingSetting.Duration = duration;
94                     meetingSetting.MeetingKey = sco_id;
95                     context.Meetings.InsertOnSubmit(meeting);
96                     context.MeetingSettings.InsertOnSubmit(meetingSetting);
97                 }
98                 context.SubmitChanges();
99             } catch (Exception ex) {
100                 Extensions.LogServiceError("SetMeetingSettings", ex);
101                 result.Success = false;
102                 result.Message = ex.Message;
103             }
104             return result;
105         }
106
107         [WebMethod]
108         public SessionStatusClass SessionStatus(int sco_id) {
109             Extensions.LogServiceCall("[service][SessionStatus]", string.Format("Parameters: sco_id = {0}", sco_id));
110             var result = new SessionStatusClass {
111                 MeetingSessionKey = -1,
112                 OwnerPrincipalId = -1,
113                 Status = "none"
114             };
115             try {
116                 var context = Database;
117                 var existingSession =
118                     context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue)
119                            .OrderByDescending(ms => ms.StartDate)
120                            .FirstOrDefault();
121                 if(existingSession != null) {
122                     result.MeetingSessionKey = existingSession.MeetingSessionKey;
123                     result.OwnerPrincipalId = existingSession.OwnerPrincipal_ID;
124                     var isPaused = existingSession.MeetingSessionPauses.Count(me => !me.EndDate.HasValue) > 0;
125                     if(existingSession.EndDate.HasValue) {
126                         result.Status = "complete";
127                     } else if(!existingSession.EndDate.HasValue && existingSession.StartDate < DateTime.UtcNow && !isPaused) {
128                         result.Status = "active";
129                     } else if(isPaused) {
130                         result.Status = "pause";
131                     }
132                 }
133             } catch (Exception ex) {
134                 Extensions.LogServiceError("SessionStatus", ex);
135                 result.OwnerPrincipalId = -1;
136                 result.Status = "error";
137             }
138             return result;
139         }
140
141         [WebMethod]
142         public int StartSession(int sco_id, string name, int owner_principal_id) {
143             Extensions.LogServiceCall("StartSession", string.Format("Parameters: sco_id = {0} name= {1} owner_principal_id = {2}", sco_id, name, owner_principal_id));
144             var result = -1;
145             try {
146                 var context = Database;
147                 var existingSession =
148                     context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue)
149                            .OrderByDescending(ms => ms.StartDate)
150                            .FirstOrDefault();
151                 if(existingSession == null) {
152                     //create new session
153                     var meeting = context.Meetings.SingleOrDefault(m => m.SCO_ID == sco_id);
154                     if(meeting == null) {
155                         meeting = new Meeting {
156                             SCO_ID = sco_id,
157                             MeetingKey = sco_id
158                         };
159                         context.Meetings.InsertOnSubmit(meeting);
160                         var meetingSettings = new MeetingSetting {
161                             Interval = 15,
162                             MeetingKey = sco_id,
163                             Text = "Respond"
164                         };
165                         context.MeetingSettings.InsertOnSubmit(meetingSettings);
166                         var meetingDetails = new MeetingDetail {
167                             MeetingKey = sco_id,
168                             CourseCode = "",
169                             Instructor = "",
170                             Location = "",
171                             TopicID = "",
172                             TopicName = ""
173                         };
174                         context.MeetingDetails.InsertOnSubmit(meetingDetails);
175                     }
176                     var session = new MeetingSession {
177                         SCO_ID = sco_id,
178                         Name = name,
179                         StartDate = DateTime.UtcNow, //start
180                         OwnerPrincipal_ID = owner_principal_id
181                     };
182                     context.MeetingSessions.InsertOnSubmit(session);
183                     context.SubmitChanges();
184                     result = session.MeetingSessionKey;
185                     //check for multiple sessions
186                     var existingSessionCount = context.MeetingSessions.Count(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue);
187                     if(existingSessionCount > 1) {
188                         //there should only be one session
189                         var existingSessions = context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue)
190                                                       .OrderBy(ms => ms.MeetingSessionKey);
191                         var sessionToKeep = existingSessions.First();
192                         var sessionsToDelete =
193                             existingSessions.Where(ms => ms.MeetingSessionKey != sessionToKeep.MeetingSessionKey);
194                         foreach (var meetingSession in sessionsToDelete) {
195                             if(meetingSession.MeetingParticipantSessions.Any() || meetingSession.MeetingSessionHeartbeats.Any()) {
196                                 //session has data, do not delete it
197                             } else {
198                                 context.MeetingSessions.DeleteOnSubmit(meetingSession);
199                             }
200                         }
201                         try {
202                             context.SubmitChanges();
203                         } catch (Exception ex) {
204                             Extensions.LogServiceError("StartSession", ex);
205                         }
206                     }
207                 } else {
208                     //session already exists, this should not happen!
209                     //string existingSessionKeys = string.Join(",", context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue).Select(ms => ms.MeetingSessionKey).ToArray());
210                     //throw new ArgumentException(String.Format("Trying to start a session which already exists.  sco_id = {0}, meetingSessionKeys = {1}", sco_id, existingSessionKeys));
211                     result = existingSession.MeetingSessionKey;
212                 }
213             } catch (Exception ex) {
214                 Extensions.LogServiceError("StartSession", ex);
215                 result = -1;
216             }
217             return result;
218         }
219
220
221         private void startNotify(string args) {
222             var startinfo = new ProcessStartInfo
223             {
224                 FileName = $"{notifyFilePath}",
225                 CreateNoWindow = true,
226                 UseShellExecute = false,
227                 WindowStyle = ProcessWindowStyle.Hidden,
228                 Arguments = args
229             };
230             var process = Process.Start(startinfo);
231             process.WaitForExit(0);
232         }
233
234
235         [WebMethod]
236         public int StopSession(int sessionKey, DateTime end) {
237             Extensions.LogServiceCall("[service.asmx][StopSession]", string.Format("Parameters: sessionKey = {0} end = {1}", sessionKey, end.ToString("yyyy-MM-dd HH:mm:ss.fff")));
238             var result = -1;
239             var session = Database.MeetingSessions.Single(ms => ms.MeetingSessionKey == sessionKey);
240             try
241             {
242                 session.EndDate = DateTime.UtcNow; //end
243                 var participants = Database.MeetingParticipantSessions.Where(m => m.MeetingSessionKey == session.MeetingSessionKey);
244                 foreach (var participantSession in participants) {
245                     foreach (var participantTracking in participantSession.ParticipantTrackings.Where(t => !t.EndDate.HasValue)) {
246                         participantTracking.EndDate = session.EndDate;
247                     }
248                 }
249
250                 adobe.GetAdobeTransactions(session, Database);
251                 // Tyler Allen - 08/27/2016
252                 // This is redundant and will cause deadlocks
253                 //Database.SubmitChanges();
254
255                 result = session.MeetingSessionKey;
256                 CleanupSessionEngagements(session.MeetingSessionKey);
257
258
259                 
260
261             }
262             catch (Exception ex) {
263                 Extensions.LogServiceError("[service.asmx][StopSession]", ex);
264                 result = -1;
265             }
266
267             /* This is the new modifications created by Tyler Allen [08/22/2016] */
268
269             // TODO: Depricated for new Notify application
270             // Launch a non-blocking thread to wait for 30 minutes for adobe results then email certificates
271             // var waitForResultsThread = new Thread(() => StopSessionResultsThread(session));
272             // waitForResultsThread.Start();
273
274             // -u = unattended
275             // -s = stop session
276             try {
277                 Extensions.LogServiceCall("[service.asmx][StopSession][CPE.App.Notify.exe]", $"Processing CPE.App.Notify.exe [{notifyFilePath} -u -s {session.MeetingSessionKey}]");
278                 //Process.Start($"{notifyFilePath} -u -s {session.MeetingSessionKey}");
279                 startNotify($"-u -s {session.MeetingSessionKey}");
280                 Extensions.LogServiceCall("[service.asmx][StopSession][CPE.App.Notify.exe]", $"Started CPE.App.Notify.exe [{notifyFilePath} -u -s {session.MeetingSessionKey}]");
281             } catch (Exception exception) {
282                 Extensions.LogServiceError("[service.asmx][StopSession][CPE.App.Notify.exe]", exception);
283             }
284
285             /* This is the new modifications created by Tyler Allen [08/22/2016] */
286
287             return sessionKey;
288         }
289
290         [WebMethod]
291         public int PauseSession(int sessionKey, DateTime start) {
292             Extensions.LogServiceCall("PauseSession", string.Format("Parameters: sessionKey = {0} start= {1}", sessionKey, start.ToString("yyyy-MM-dd HH:mm:ss.fff")));
293             var result = -1;
294             try {
295                 var context = Database;
296                 var sessionEvent = new MeetingSessionPause {
297                     MeetingSessionKey = sessionKey,
298                     StartDate = DateTime.UtcNow //start
299                 };
300                 context.MeetingSessionPauses.InsertOnSubmit(sessionEvent);
301                 context.SubmitChanges();
302                 result = sessionEvent.MesstingSessionPauseKey;
303             } catch (Exception ex) {
304                 Extensions.LogServiceError("PauseSession", ex);
305                 result = -1;
306             }
307             return result;
308         }
309
310         [WebMethod]
311         public int GetPauseKey(int sco_id) {
312             Extensions.LogServiceCall("GetPauseKey", string.Format("Parameters: sco_id = {0}", sco_id));
313             var result = -1;
314             try {
315                 var context = Database;
316                 var existingSession =
317                     context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue)
318                            .OrderByDescending(ms => ms.StartDate)
319                            .FirstOrDefault();
320                 if(existingSession != null) {
321                     var pause = existingSession.MeetingSessionPauses.Where(mse => !mse.EndDate.HasValue)
322                                                .OrderByDescending(
323                                                                   mse => mse.StartDate)
324                                                .First();
325                     result = pause.MesstingSessionPauseKey;
326                 }
327                 ;
328             } catch (Exception ex) {
329                 Extensions.LogServiceError("GetPauseKey", ex);
330                 result = -1;
331             }
332             return result;
333         }
334
335         [WebMethod]
336         public int ResumeSession(int eventKey, DateTime end) {
337             Extensions.LogServiceCall("ResumeSession", string.Format("Parameters: eventKey = {0} end = {1}", eventKey, end.ToString("yyyy-MM-dd HH:mm:ss.fff")));
338             var result = -1;
339             try {
340                 var context = Database;
341                 var sessionEvent = context.MeetingSessionPauses.Single(se => se.MesstingSessionPauseKey == eventKey);
342                 sessionEvent.EndDate = DateTime.UtcNow; //end;
343                 context.SubmitChanges();
344                 result = sessionEvent.MesstingSessionPauseKey;
345             } catch (Exception ex) {
346                 Extensions.LogServiceError("ResumeSession", ex);
347                 result = -1;
348             }
349             return result;
350         }
351
352         [WebMethod]
353         public ServiceStatus LogEngagement(int meetingSessionKey) {
354             Extensions.LogServiceCall("LogEngagement", string.Format("Parameters: meetingSessionKey = {0}", meetingSessionKey));
355             var result = new ServiceStatus {
356                 Success = true,
357                 Message = ""
358             };
359             try {
360                 var context = Database;
361                 var record = new MeetingSessionHeartbeat {
362                     MeetingSessionKey = meetingSessionKey,
363                     Timestamp = DateTime.UtcNow
364                 };
365                 context.MeetingSessionHeartbeats.InsertOnSubmit(record);
366                 context.SubmitChanges();
367             } catch (Exception ex) {
368                 Extensions.LogServiceError("LogEngagement", ex);
369                 result.Success = false;
370                 result.Message = ex.Message;
371             }
372             return result;
373         }
374
375         [WebMethod]
376         public ServiceStatus TrackUser(int meetingSessionKey, int principal_id, string firstname, string lastname, string email, byte type) {
377             Extensions.LogServiceCall("TrackUser", string.Format("Parameters: meetingSessionKey = {0} principal_id= {1} firstname = {2}, lastname = {3} email = {4} type = {5} DateTimeUtc = {6}", meetingSessionKey, principal_id, firstname, lastname, email, type, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
378             var result = new ServiceStatus {
379                 Success = true,
380                 Message = ""
381             };
382             try {
383                 if(principal_id > 0) {
384                     var context = Database;
385                     //var meeting = context.Meetings.SingleOrDefault(m => m.SCO_ID == sco_id);
386                     //JM - 9/27/2012 changed session to add a check for session not ended
387                     //var session =
388                     //    context.MeetingSessions.SingleOrDefault(ms => ms.MeetingSessionKey == meetingSessionKey && !ms.EndDate.HasValue);
389                     //JM - 11/20/2012 changed to allow for end dates existing for recordings
390                     var session = getActiveSessionByKey(meetingSessionKey);
391                     if(session != null) {
392                         var user = context.Participants.SingleOrDefault(p => p.Principal_ID == principal_id);
393                         if(user == null) {
394                             //create new user
395                             user = new Participant {
396                                 Principal_ID = principal_id,
397                                 FirstName = firstname,
398                                 LastName = lastname,
399                                 Email = email
400                             };
401                             context.Participants.InsertOnSubmit(user);
402                         }
403                         //else
404                         //{
405                         //    user.FirstName = firstname;
406                         //    user.LastName = lastname;
407                         //    user.Email = email;
408                         //}
409                         //JM 9/27/2012 - changed to grab first participant session found.  Weird issue where some users were getting multiple sesions  
410                         var existingMeetingParticipantSession = session.MeetingParticipantSessions.OrderBy(mps => mps.MeetingParticipantSessionKey)
411                                                                        .FirstOrDefault(mps => mps.ParticipantKey == principal_id);
412                         if(existingMeetingParticipantSession == null) {
413                             //create new session for user
414                             existingMeetingParticipantSession = new MeetingParticipantSession {
415                                 MeetingSessionKey = session.MeetingSessionKey,
416                                 ParticipantKey = principal_id,
417                                 PlaySound = true,
418                                 Created = DateTime.UtcNow
419                             };
420                             context.MeetingParticipantSessions.InsertOnSubmit(existingMeetingParticipantSession);
421                             context.SubmitChanges();
422                             //JM-12/27/2012 Change to check for multiple meeting participant sessions
423                             CheckParticipantSessions(session, principal_id);
424                             //make sure tracking gets assigned right key 
425                             existingMeetingParticipantSession = context.MeetingParticipantSessions.OrderBy(p => p.MeetingParticipantSessionKey)
426                                                                        .FirstOrDefault(
427                                                                                        p => p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principal_id);
428                         }
429                         var lastParticipantTracking =
430                             context.ParticipantTrackings.Where(
431                                                                pt =>
432                                                                pt.MeetingParticipantSessionKey ==
433                                                                existingMeetingParticipantSession.MeetingParticipantSessionKey &&
434                                                                pt.Principal_ID == principal_id)
435                                    .OrderByDescending(p => p.StartDate)
436                                    .FirstOrDefault();
437                         if(type == 0) {
438                             // Start
439                             if(lastParticipantTracking != null && !lastParticipantTracking.EndDate.HasValue) {
440                                 var lastEngagement = lastParticipantTracking.MeetingParticipantSession.ParticipantEngagements.OrderByDescending(e => e.DisplayTime)
441                                                                             .FirstOrDefault();
442                                 if(lastEngagement == null)
443                                     lastParticipantTracking.EndDate = lastParticipantTracking.StartDate;
444                                 else {
445                                     lastParticipantTracking.EndDate = lastEngagement.ResponseTime.HasValue
446                                                                           ? lastEngagement.ResponseTime.Value
447                                                                           : lastEngagement.DisplayTime;
448                                     if(lastParticipantTracking.EndDate < lastParticipantTracking.StartDate)
449                                         lastParticipantTracking.EndDate = lastParticipantTracking.StartDate;
450                                 }
451                             }
452                             var pTrack = new ParticipantTracking {
453                                 MeetingParticipantSessionKey =
454                                     existingMeetingParticipantSession.MeetingParticipantSessionKey,
455                                 Principal_ID = principal_id,
456                                 StartDate = DateTime.UtcNow //timestamp
457                             };
458                             context.ParticipantTrackings.InsertOnSubmit(pTrack);
459                         } else {
460                             // End
461                             if(lastParticipantTracking == null)
462                                 throw new ArgumentException(string.Format("Exiting user does not have an entrance time MeetingSessionKey = {0}, principalId = {1}", meetingSessionKey, principal_id));
463                             lastParticipantTracking.EndDate = DateTime.UtcNow;
464                         }
465                         context.SubmitChanges();
466                         CheckParticipantSessions(session, principal_id);
467                     } else {
468                         result.Success = false;
469                         result.Message = "MeetingSessionKey: " + meetingSessionKey + " is not valid";
470                         throw new ArgumentException(result.Message);
471                     }
472                 } else {
473                     result.Success = false;
474                     result.Message = "Principal ID: " + principal_id + " is not valid";
475                     throw new ArgumentException(result.Message);
476                 }
477             } catch (Exception ex) {
478                 Extensions.LogServiceError("TrackUser", ex);
479                 result.Success = false;
480                 result.Message = ex.Message;
481             }
482             return result;
483         }
484
485         [WebMethod]
486         public int TrackEngagementReceived(int scoId, int principalId, double timeDelta) {
487             Extensions.LogServiceCall("TrackEngagementReceived", string.Format("Parameters: scoId = {0} principalId = {1} timeDelta = {2} DateTimeUtc = {3}", scoId, principalId, timeDelta, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
488             var result = -1;
489             var context = Database;
490             try {
491                 if(principalId > 0) {
492                     //var session = context.MeetingSessions.OrderByDescending(ms => ms.StartDate).FirstOrDefault(m => m.SCO_ID == scoId & !m.EndDate.HasValue);
493                     //JM - 11/20/2012 changed to allow for session end dates existing for recordings
494                     var session = getActiveSessionBySco(scoId);
495                     if(session != null) {
496                         CheckParticipantSessions(session, principalId);
497                         var participantSession =
498                             context.MeetingParticipantSessions.OrderBy(p => p.MeetingParticipantSessionKey)
499                                    .
500                                     FirstOrDefault(
501                                                    p =>
502                                                    p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principalId);
503                         if(participantSession == null) {
504                             participantSession = new MeetingParticipantSession {
505                                 MeetingSessionKey = session.MeetingSessionKey,
506                                 ParticipantKey = principalId,
507                                 PlaySound = true,
508                                 Created = DateTime.UtcNow
509                             };
510                             context.MeetingParticipantSessions.InsertOnSubmit(participantSession);
511                             context.SubmitChanges();
512                             //JM-12/27/2012 Change to check for multiple meeting participant sessions
513                             CheckParticipantSessions(session, principalId);
514                             participantSession = context.MeetingParticipantSessions.OrderBy(
515                                                                                             p => p.MeetingParticipantSessionKey)
516                                                         .FirstOrDefault(
517                                                                         p =>
518                                                                         p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principalId);
519                             //Check Participant Tracking
520                             CheckParticipantTracking(participantSession, principalId);
521                         }
522                         //else
523                         //{
524                         //    //Check Participant Tracking
525                         //    CheckParticipantTracking(participantSession, principalId);
526                         //}
527                         var now = DateTime.UtcNow;
528                         var engagementVerification = new ParticipantEngagementVerification {
529                             MeetingParticipantSessionKey =
530                                 participantSession.MeetingParticipantSessionKey,
531                             Principal_ID = principalId,
532                             DisplayTime = now,
533                             PreviousAlertDelta = timeDelta
534                         };
535                         context.ParticipantEngagementVerifications.InsertOnSubmit(engagementVerification);
536                         context.SubmitChanges();
537                         result = engagementVerification.TrackingKey;
538                     } else {
539                         throw new ArgumentException(string.Format("No session exists for sco = {0}, principal_id = {1}", scoId, principalId));
540                     }
541                 } else {
542                     throw new ArgumentException("Principal ID: " + principalId + " is not valid");
543                 }
544             } catch (Exception ex) {
545                 Extensions.LogServiceError("TrackEngagementReceived", ex);
546                 result = -1;
547             }
548             return result;
549         }
550
551         [WebMethod]
552         public int TrackEngagementDisplay(int scoId, int principalId) {
553             Extensions.LogServiceCall("TrackEngagementDisplay", string.Format("Parameters: scoId = {0} principalId = {1} DateTimeUtc = {2}", scoId, principalId, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
554             var result = -1;
555             try {
556                 if(principalId > 0) {
557                     var context = Database;
558                     //var session = context.MeetingSessions.OrderByDescending(ms => ms.StartDate).FirstOrDefault(m => m.SCO_ID == scoId & !m.EndDate.HasValue);
559                     //JM - 11/20/2012 changed to allow for session end dates existing for recordings
560                     var session = getActiveSessionBySco(scoId);
561                     if(session != null) {
562                         CheckParticipantSessions(session, principalId);
563                         var participantSession =
564                             context.MeetingParticipantSessions.OrderBy(p => p.MeetingParticipantSessionKey)
565                                    .FirstOrDefault(
566                                                    p => p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principalId);
567                         if(participantSession == null) {
568                             participantSession = new MeetingParticipantSession {
569                                 MeetingSessionKey = session.MeetingSessionKey,
570                                 ParticipantKey = principalId,
571                                 PlaySound = true,
572                                 Created = DateTime.UtcNow
573                             };
574                             context.MeetingParticipantSessions.InsertOnSubmit(participantSession);
575                             context.SubmitChanges();
576                             //JM-12/27/2012 Change to check for multiple meeting participant sessions
577                             CheckParticipantSessions(session, principalId);
578                             //make sure tracking gets assigned right key 
579                             participantSession = context.MeetingParticipantSessions.OrderBy(p => p.MeetingParticipantSessionKey)
580                                                         .FirstOrDefault(
581                                                                         p => p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principalId);
582                             //Check Participant Tracking
583                             CheckParticipantTracking(participantSession, principalId);
584                         } else {
585                             //Check Participant Tracking
586                             CheckParticipantTracking(participantSession, principalId);
587                         }
588                         //var validEngagement = false;
589                         ////Validate the engangement request to make sure it was sent by the host within the last 30 seconds
590                         //var now = DateTime.UtcNow;
591                         //var hostPoll = session.MeetingSessionHeartbeats.OrderByDescending(h => h.Timestamp).FirstOrDefault();
592                         //if (hostPoll != null)
593                         //{
594                         //    var timeDelta = Math.Abs((now - hostPoll.Timestamp).TotalSeconds);
595                         //    validEngagement = timeDelta <= 30.0;
596                         //}
597                         //if (validEngagement)
598                         //{
599                         //    var engagement = new ParticipantEngagement()
600                         //                         {
601                         //                             MeetingParticipantSessionKey =
602                         //                                 participantSession.MeetingParticipantSessionKey,
603                         //                             Principal_ID = principalId,
604                         //                             DisplayTime = now
605                         //                         };
606                         //    context.ParticipantEngagements.InsertOnSubmit(engagement);
607                         //    context.SubmitChanges();
608                         //    result = engagement.ParticipantEngagementKey;
609                         //}
610                         var lastHeartbeat = session.MeetingSessionHeartbeats.OrderBy(h => h.Timestamp)
611                                                    .Last();
612                         var lastEngagement = participantSession.ParticipantEngagements.Where(t => t.Principal_ID == principalId)
613                                                                .OrderBy(t => t.DisplayTime)
614                                                                .LastOrDefault();
615                         if(lastEngagement == null || lastEngagement.DisplayTime < lastHeartbeat.Timestamp) {
616                             var now = DateTime.UtcNow;
617                             var engagement = new ParticipantEngagement {
618                                 MeetingParticipantSessionKey =
619                                     participantSession.MeetingParticipantSessionKey,
620                                 Principal_ID = principalId,
621                                 DisplayTime = now
622                             };
623                             context.ParticipantEngagements.InsertOnSubmit(engagement);
624                             context.SubmitChanges();
625                             result = engagement.ParticipantEngagementKey;
626                         } else
627                             result = lastEngagement.ParticipantEngagementKey;
628                     } else {
629                         throw new ArgumentException(string.Format("No session exists for sco = {0}, principal_id = {1}", scoId,
630                                                                   principalId));
631                     }
632                 } else {
633                     throw new ArgumentException("Principal ID: " + principalId + " is not valid");
634                 }
635             } catch (Exception ex) {
636                 Extensions.LogServiceError("TrackEngagementDisplay", ex);
637                 result = -99;
638             }
639             return result;
640         }
641
642         [WebMethod]
643         public int TrackEngagementResponse(int scoId, int principalId) //int engagementKey
644         {
645             Extensions.LogServiceCall("TrackEngagementResponse", string.Format("Parameters: scoId = {0} principalId = {1} DateTimeUtc = {2}", scoId, principalId, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
646             var result = -1;
647             try {
648                 if(principalId > 0) {
649                     var context = Database;
650                     var now = DateTime.UtcNow;
651                     //JM - 9-21-2012 changing to ignore key - will require updating pod
652                     //var engagement = context.ParticipantEngagements.Single(pe => pe.ParticipantEngagementKey == engagementKey);
653                     //var session =
654                     //    Database.MeetingSessions.OrderByDescending(ms => ms.StartDate).FirstOrDefault(ms => ms.SCO_ID == scoId && !ms.EndDate.HasValue);
655                     //JM - 11/20/2012 changed to allow for session end dates existing for recordings
656                     var session = getActiveSessionBySco(scoId);
657                     if(session != null) {
658                         CheckParticipantSessions(session, principalId);
659                         //JM - 9-27-2012 Changed to grab first session.  Fix for weird issue where some users were getting mutiple user sessions in a meeting.
660                         var participantSession = context.MeetingParticipantSessions.Where(mps =>
661                                                                                           mps.MeetingSessionKey == session.MeetingSessionKey &&
662                                                                                           mps.ParticipantKey == principalId)
663                                                         .OrderBy(mps => mps.MeetingParticipantSessionKey)
664                                                         .FirstOrDefault();
665                         //var participantSession =
666                         //    context.MeetingParticipantSessions.Single(
667                         //        mps =>
668                         //        mps.MeetingSessionKey == session.MeetingSessionKey && mps.ParticipantKey == principalId);
669                         if(participantSession == null) {
670                             //create participant session
671                             participantSession = new MeetingParticipantSession {
672                                 MeetingSessionKey = session.MeetingSessionKey,
673                                 ParticipantKey = principalId,
674                                 PlaySound = true,
675                                 Created = DateTime.UtcNow
676                             };
677                             context.MeetingParticipantSessions.InsertOnSubmit(participantSession);
678                             context.SubmitChanges();
679                             //JM-12/27/2012 Change to check for multiple meeting participant sessions
680                             CheckParticipantSessions(session, principalId);
681                             participantSession = context.MeetingParticipantSessions.OrderBy(p => p.MeetingParticipantSessionKey)
682                                                         .FirstOrDefault(
683                                                                         p => p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principalId);
684                             //Check Participant Tracking
685                             CheckParticipantTracking(participantSession, principalId);
686                         } else {
687                             //Check Participant Tracking
688                             CheckParticipantTracking(participantSession, principalId);
689                         }
690                         //find last engagement without a response within 90 seconds
691                         var participantEngagement = context.ParticipantEngagements.OrderByDescending(pe => pe.DisplayTime)
692                                                            .
693                                                             FirstOrDefault(
694                                                                            pe =>
695                                                                            pe.MeetingParticipantSessionKey == participantSession.MeetingParticipantSessionKey
696                                                                            && (Math.Abs((now - pe.DisplayTime).TotalSeconds) <= 90)
697                                                                            && !pe.ResponseTime.HasValue);
698                         if(participantEngagement != null) {
699                             //found it
700                             participantEngagement.ResponseTime = now;
701                         }
702                         //else
703                         //{
704                         //    //not found, create new engagement
705                         //    participantEngagement = new ParticipantEngagement()
706                         //                                {
707                         //                                    MeetingParticipantSessionKey =
708                         //                                        participantSession.MeetingParticipantSessionKey,
709                         //                                    Principal_ID = principalId,
710                         //                                    DisplayTime = now,
711                         //                                    ResponseTime = now
712                         //                                };
713                         //    context.ParticipantEngagements.InsertOnSubmit(participantEngagement);
714                         //}
715                         context.SubmitChanges();
716                         result = 1; //participantEngagement.ParticipantEngagementKey;
717                     } else {
718                         throw new ArgumentException(string.Format("No session exists for sco = {0}, principal_id = {1}", scoId,
719                                                                   principalId));
720                     }
721                 } else {
722                     throw new ArgumentException("Principal ID: " + principalId + " is not valid");
723                 }
724             } catch (Exception ex) {
725                 Extensions.LogServiceError("TrackEngagementResponse", ex);
726                 result = -1;
727             }
728             return result;
729         }
730
731         [WebMethod]
732         public Detail GetMeetingDetails(int sco_id) {
733             Extensions.LogServiceCall("[service][GetMeetingDetails]", string.Format("Parameters: sco_id = {0}", sco_id));
734             var result = new Detail {
735                 CourseCode = "",
736                 Instructor = "",
737                 Location = "",
738                 TopicID = "",
739                 Topic = ""
740             };
741             var meetingDetail = Database.MeetingDetails.SingleOrDefault(ms => ms.MeetingKey == sco_id);
742             if(meetingDetail != null) {
743                 result.CourseCode = meetingDetail.CourseCode;
744                 result.Instructor = meetingDetail.Instructor;
745                 result.Location = meetingDetail.Location;
746                 result.TopicID = meetingDetail.TopicID;
747                 result.Topic = meetingDetail.TopicName;
748             }
749             return result;
750         }
751
752         [WebMethod]
753         public ServiceStatus SetMeetingDetails(int sco_id, string courseCode, string instructor, string location, string topicID, string topic) {
754             Extensions.LogServiceCall("SetMeetingDetails", string.Format("Parameters: sco_id = {0}, courseCode = {1} instructor = {2} location = {3} topicId = {4} topic = {5}", sco_id, courseCode, instructor, location, topicID, topic));
755             var result = new ServiceStatus {
756                 Success = true,
757                 Message = ""
758             };
759             try {
760                 var context = Database;
761                 var existing = context.Meetings.SingleOrDefault(m => m.SCO_ID == sco_id);
762                 if(existing != null) {
763                     if(existing.MeetingDetail == null) {
764                         var meetingDetail = new MeetingDetail {
765                             MeetingKey = sco_id,
766                             CourseCode = courseCode,
767                             Instructor = instructor,
768                             Location = location,
769                             TopicID = topicID,
770                             TopicName = topic
771                         };
772                         context.MeetingDetails.InsertOnSubmit(meetingDetail);
773                     } else {
774                         existing.MeetingDetail.CourseCode = courseCode;
775                         existing.MeetingDetail.Instructor = instructor;
776                         existing.MeetingDetail.Location = location;
777                         existing.MeetingDetail.TopicID = topicID;
778                         existing.MeetingDetail.TopicName = topic;
779                     }
780                 } else {
781                     var meeting = new Meeting {
782                         SCO_ID = sco_id,
783                         MeetingKey = sco_id
784                     };
785                     var meetingDetail = new MeetingDetail();
786                     meetingDetail.CourseCode = courseCode;
787                     meetingDetail.Instructor = instructor;
788                     meetingDetail.Location = location;
789                     meetingDetail.TopicID = topicID;
790                     meetingDetail.TopicName = topic;
791                     meetingDetail.MeetingKey = sco_id;
792                     context.Meetings.InsertOnSubmit(meeting);
793                     context.MeetingDetails.InsertOnSubmit(meetingDetail);
794                 }
795                 context.SubmitChanges();
796             } catch (Exception ex) {
797                 Extensions.LogServiceError("SetMeetingDetails", ex);
798                 result.Success = false;
799                 result.Message = ex.Message;
800             }
801             return result;
802         }
803
804         [WebMethod]
805         public long GetTotalSessionTime(int sco_id) {
806             Extensions.LogServiceCall("GetTotalSessionTime", string.Format("Parameters: sco_id = {0}", sco_id));
807             long result = -1;
808             try {
809                 result = 0;
810                 var context = Database;
811                 var session = context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue)
812                                      .OrderByDescending(ms => ms.StartDate)
813                                      .FirstOrDefault();
814                 if(session != null) {
815                     if(session.EndDate.HasValue) {
816                         result = session.EndDate.Value.Ticks - session.StartDate.Ticks;
817                     } else {
818                         result = DateTime.UtcNow.Ticks - session.StartDate.Ticks;
819                     }
820                     var completedPauses = session.MeetingSessionPauses.Where(msp => msp.EndDate.HasValue);
821                     result = completedPauses.Aggregate(result, (current, meetingSessionPause) => current - (meetingSessionPause.EndDate.Value.Ticks - meetingSessionPause.StartDate.Ticks));
822                     var incompletePause = session.MeetingSessionPauses.Where(msp => !msp.EndDate.HasValue)
823                                                  .OrderByDescending(msp => msp.StartDate)
824                                                  .FirstOrDefault();
825                     if(incompletePause != null) {
826                         result -= DateTime.UtcNow.Ticks - incompletePause.StartDate.Ticks;
827                     }
828                 }
829                 result = result/TimeSpan.TicksPerMillisecond;
830             } catch (Exception ex) {
831                 Extensions.LogServiceError("GetTotalSessionTime", ex);
832                 result = -1;
833             }
834             return result;
835         }
836
837         [WebMethod]
838         public int ActiveSessionCount(int sco_id) {
839             Extensions.LogServiceCall("ActiveSessionCount", string.Format("Parameters: sco_id = {0}", sco_id));
840             var result = 1;
841             try {
842                 var context = Database;
843                 result = context.MeetingSessions.Count(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue);
844             } catch (Exception ex) {
845                 Extensions.LogServiceError("ActiveSessionCount", ex);
846                 result = -1;
847             }
848             return result;
849         }
850
851         [WebMethod]
852         public int EndAllActiveSessions(int sco_id) {
853             Extensions.LogServiceCall("EndAllActiveSessions", string.Format("Parameters: sco_id = {0}", sco_id));
854             var result = 1;
855             try {
856                 var now = DateTime.UtcNow;
857                 var context = Database;
858                 var activeSessions = context.MeetingSessions.Where(ms => ms.SCO_ID == sco_id && !ms.EndDate.HasValue);
859                 foreach (var meetingSession in activeSessions) {
860                     meetingSession.EndDate = now;
861                 }
862                 context.SubmitChanges();
863             } catch (Exception ex) {
864                 Extensions.LogServiceError("EndAllActveSessions", ex);
865                 result = -1;
866             }
867             return result;
868         }
869
870         [WebMethod]
871         public int RecordHeartbeatTick(int meetingSessionKey, int principal_id, double previousInterval) {
872             Extensions.LogServiceCall("RecordHeartbeatTick", string.Format("Parameters: meetingSessionKey= {0}, principal_id = {1}, previousInterval = {2}", meetingSessionKey, principal_id, previousInterval));
873             var result = -1;
874             try {
875                 var context = Database;
876                 var tickRecord = new MeetingSessionHeartbeatTick {
877                     MeetingSessionKey = meetingSessionKey,
878                     Timestamp = DateTime.UtcNow,
879                     Principal_ID = principal_id,
880                     PreviousInterval = previousInterval
881                 };
882                 context.MeetingSessionHeartbeatTicks.InsertOnSubmit(tickRecord);
883                 context.SubmitChanges();
884                 result = 1;
885             } catch (Exception ex) {
886                 Extensions.LogServiceError("RecordHeartbeatTick", ex);
887                 result = -1;
888             }
889             return result;
890         }
891
892         [WebMethod]
893         public bool RecordParticipantLog(int scoId, int principalId, string details) {
894             Extensions.LogServiceCall("RecordParticipantLog", string.Format("Parameters: sco_id = {0} principal_id = {1} details = {2} DateTimeUtc = {3}", scoId, principalId, details, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
895             try {
896                 var context = Database;
897                 var log = new ParticipantLog {
898                     Sco_ID = scoId,
899                     Principal_ID = principalId,
900                     Details = HttpUtility.HtmlDecode(details.Replace("_cr_", "\r\n")),
901                     Created = DateTime.UtcNow
902                 };
903                 context.ParticipantLogs.InsertOnSubmit(log);
904                 context.SubmitChanges();
905             } catch (Exception ex) {
906                 Extensions.LogServiceError("RecordParticipantLog", ex);
907             }
908             return true;
909         }
910
911         [WebMethod]
912         public bool RecordHostMessage(int scoId, int principalId, string message) {
913             Extensions.LogServiceCall("RecordHostMessage", string.Format("Parameters: sco_id = {0} principal_id = {1} message = {2} DateTimeUtc = {3}", scoId, principalId, message, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
914             try {
915                 var context = Database;
916                 var log = new HostMessageLog {
917                     SCO_ID = scoId,
918                     Principal_ID = principalId,
919                     Message = message
920                 };
921                 context.HostMessageLogs.InsertOnSubmit(log);
922                 context.SubmitChanges();
923             } catch (Exception ex) {
924                 Extensions.LogServiceError("RecordHostMessage", ex);
925             }
926             return true;
927         }
928
929         [WebMethod]
930         public bool RecordParticipantMessage(int scoId, int principalId, string message) {
931             Extensions.LogServiceCall("RecordParticipantMessage", string.Format("Parameters: sco_id = {0} principal_id = {1} details = {2} DateTimeUtc = {3}", scoId, principalId, message, DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff")));
932             try {
933                 var context = Database;
934                 var log = new ParticipantMessageLog {
935                     SCO_ID = scoId,
936                     Principal_ID = principalId,
937                     Message = message
938                 };
939                 context.ParticipantMessageLogs.InsertOnSubmit(log);
940                 context.SubmitChanges();
941             } catch (Exception ex) {
942                 Extensions.LogServiceError("RecordParticipantMessage", ex);
943             }
944             return true;
945         }
946
947         [WebMethod]
948         public ServiceStatus ArchiveSessionEnd(int scoId, int principalId, string firstname, string lastname, string email) {
949             Extensions.LogServiceCall("[service.asmx][ArchiveSessionEnd]", string.Format("Parameters: scoId = {0} principalId = {1} firstname = {2} lastname = {3} email = {4}", scoId, principalId, firstname, lastname, email));
950             var result = new ServiceStatus {
951                 Success = true,
952                 Message = ""
953             };
954             MeetingParticipantSession existingMeetingParticipantSession = null;
955             try {
956                 if(principalId > 0) {
957                     var context = Database;
958                     //var meeting = context.Meetings.SingleOrDefault(m => m.SCO_ID == sco_id);
959                     //JM - 9/27/2012 changed session to add a check for session not ended
960                     var session = GetRecordingSession(scoId, context);
961                     if(session != null) {
962                         var user = context.Participants.SingleOrDefault(p => p.Principal_ID == principalId);
963                         if(user == null) {
964                             //create new user
965                             user = new Participant {
966                                 Principal_ID = principalId,
967                                 FirstName = firstname,
968                                 LastName = lastname,
969                                 Email = email
970                             };
971                             context.Participants.InsertOnSubmit(user);
972                         }
973                         //JM 9/27/2012 - changed to grab first participant session found.  Weird issue where some users were getting multiple sesions  
974                         existingMeetingParticipantSession =
975                             session.MeetingParticipantSessions.OrderBy(mps => mps.MeetingParticipantSessionKey)
976                                    .FirstOrDefault(mps => mps.ParticipantKey == principalId);
977                         if(existingMeetingParticipantSession == null) {
978                             //create new session for user
979                             existingMeetingParticipantSession = new MeetingParticipantSession {
980                                 MeetingSessionKey = session.MeetingSessionKey,
981                                 ParticipantKey = principalId,
982                                 PlaySound = true,
983                                 Created = DateTime.UtcNow
984                             };
985                             context.MeetingParticipantSessions.InsertOnSubmit(existingMeetingParticipantSession);
986                             context.SubmitChanges();
987                             //JM-12/27/2012 Change to check for multiple meeting participant sessions
988                             CheckParticipantSessions(session, principalId);
989                             //make sure tracking gets assigned right key 
990                             existingMeetingParticipantSession = context.MeetingParticipantSessions.OrderBy(p => p.MeetingParticipantSessionKey)
991                                                                        .FirstOrDefault(
992                                                                                        p => p.MeetingSessionKey == session.MeetingSessionKey & p.ParticipantKey == principalId);
993                         }
994                         var existingParticipantTracking =
995                             context.ParticipantTrackings.OrderByDescending(pt => pt.StartDate)
996                                    .FirstOrDefault(
997                                                    pt =>
998                                                    pt.MeetingParticipantSessionKey ==
999                                                    existingMeetingParticipantSession.MeetingParticipantSessionKey &&
1000                                                    pt.Principal_ID == principalId);
1001                         if(existingParticipantTracking != null) {
1002                             if(!existingParticipantTracking.EndDate.HasValue) {
1003                                 //add exit time
1004                                 existingParticipantTracking.EndDate = DateTime.UtcNow;
1005                             }
1006                         } else {
1007                             throw new ArgumentException(string.Format("Exiting user does not have an entrance time scoId = {0}, principalId = {1}, existingMeetingParticipantSession.MeetingParticipantSessionKey = {2}", scoId, principalId, existingMeetingParticipantSession.MeetingParticipantSessionKey));
1008                         }
1009
1010                         adobe.GetAdobeTransactions(session, Database);
1011                         // Tyler Allen - 08/27/2016
1012                         // This is redundant and will cause deadlocks
1013                         //Database.SubmitChanges();
1014                         CheckParticipantSessions(session, principalId);
1015
1016
1017
1018                         
1019
1020                     }
1021                     else {
1022                         result.Success = false;
1023                         result.Message = "Archive scoId: " + scoId + " is not a valid session.";
1024                         throw new ArgumentException(result.Message);
1025                     }
1026                 } else {
1027                     result.Success = false;
1028                     result.Message = "Principal ID: " + principalId + " is not valid";
1029                     throw new ArgumentException(result.Message);
1030                 }
1031             } catch (Exception ex) {
1032                 Extensions.LogServiceError("[service.asmx][ArchiveSessionEnd]", ex);
1033                 result.Success = false;
1034                 result.Message = ex.Message;
1035             } finally {
1036                 CloseArchiveSession(scoId);
1037             }
1038
1039
1040             /* This is the new modifications created by Tyler Allen [08/22/2016] */
1041
1042             // TODO: Depricated for new Notify application
1043             // Launch a non-blocking thread to wait for 30 minutes for adobe results then email certificates
1044             // System.Threading.Thread waitForResultsThread = new System.Threading.Thread(() => ArchiveSessionEndResultsThread(existingMeetingParticipantSession));
1045             // waitForResultsThread.Start();
1046
1047             // -u = unattended
1048             // -a = archive session
1049             try {
1050                 Extensions.LogServiceCall("[service.asmx][ArchiveSessionEnd][Processing]", $"{notifyFilePath} -u -a {existingMeetingParticipantSession.MeetingParticipantSessionKey}");
1051                 //Process.Start($"{notifyFilePath} -u -a {existingMeetingParticipantSession.MeetingParticipantSessionKey}");
1052                 startNotify($"-u -a {existingMeetingParticipantSession.MeetingParticipantSessionKey}");
1053                 Extensions.LogServiceCall("[service.asmx][ArchiveSessionEnd][Started]", $"{notifyFilePath} -u -a {existingMeetingParticipantSession.MeetingParticipantSessionKey}");
1054             }
1055             catch (Exception exception) {
1056                 Extensions.LogServiceError("[service.asmx][ArchiveSessionEnd][CPE.App.Notify.exe]", exception);
1057                 //Extensions.LogServiceCall("[service.asmx][ArchiveSessionEnd]", $"There was an error processing CPE.App.Notify.exe [{notifyFilePath} -u -a {existingMeetingParticipantSession.MeetingParticipantSessionKey}] ({exception.Message}");
1058             }
1059
1060             /* This is the new modifications created by Tyler Allen [08/22/2016] */
1061
1062
1063             return result;
1064         }
1065
1066         [WebMethod]
1067         public bool CloseOpenUserSessions(int meetingSessionKey) {
1068             Extensions.LogServiceCall("CloseOpenUserSessions", string.Format("Parameters: meetingSessionKey = {0}", meetingSessionKey));
1069             var result = false;
1070             try {
1071                 var session = Database.MeetingSessions.Single(ms => ms.MeetingSessionKey == meetingSessionKey);
1072                 var participants = Database.MeetingParticipantSessions.Where(m => m.MeetingSessionKey == session.MeetingSessionKey);
1073                 foreach (var participantSession in participants)
1074                     foreach (var participantTracking in participantSession.ParticipantTrackings.Where(t => !t.EndDate.HasValue)) {
1075                         // Tyler changed to get EndDate from the last engagement request.
1076                         //participantTracking.EndDate = session.EndDate;
1077                         var lastEngagement = participantTracking.MeetingParticipantSession.ParticipantEngagements.OrderByDescending(e => e.DisplayTime)
1078                                                                 .FirstOrDefault();
1079                         if(lastEngagement == null)
1080                             participantTracking.EndDate = participantTracking.StartDate;
1081                         else
1082                             participantTracking.EndDate = lastEngagement.ResponseTime.HasValue
1083                                                               ? lastEngagement.ResponseTime.Value
1084                                                               : lastEngagement.DisplayTime;
1085                     }
1086                 Database.SubmitChanges();
1087                 CleanupRebroadcastEngagements(meetingSessionKey);
1088                 result = true;
1089             } catch (Exception ex) {
1090                 Extensions.LogServiceError("CloseOpenUserSessions", ex);
1091                 result = false;
1092             }
1093             return result;
1094         }
1095
1096         private MeetingSession GetRecordingSession(int scoId, CPEWebDataContext context) {
1097             var now = DateTime.UtcNow;
1098             var twelveHoursAgo = now.AddHours(-12);
1099             var session =
1100                 context.MeetingSessions.Where(ms => ms.SCO_ID == scoId && ms.StartDate >= twelveHoursAgo && ms.StartDate < now && ms.EndDate.HasValue)
1101                        .OrderByDescending(ms => ms.EndDate)
1102                        .FirstOrDefault() ??
1103                 getLastSession(scoId);
1104             return session;
1105         }
1106
1107         //static Dictionary<string, Task<bool>> Tasks = new Dictionary<string, Task<bool>>();
1108         private void CloseArchiveSession(int scoId) {
1109             var context = Database;
1110             var session = GetRecordingSession(scoId, context);
1111             if(session != null) {
1112                 //session.EndDate = endTime;
1113                 var participantSessions = context.MeetingParticipantSessions.Where(m => m.MeetingSessionKey == session.MeetingSessionKey);
1114                 foreach (var participantSession in participantSessions) {
1115                     var trackingsWithoutExit =
1116                         context.ParticipantTrackings.Where(
1117                                                            t => t.MeetingParticipantSessionKey == participantSession.MeetingParticipantSessionKey
1118                                                                 && !t.EndDate.HasValue)
1119                                .OrderByDescending(t => t.StartDate)
1120                                .FirstOrDefault();
1121                     //TODO: Try to catch logouts due to power failure
1122                     //look for las poll displayed and compare to logout time?
1123                     if(trackingsWithoutExit != null) {
1124                         // Tyler changed to get EndDate from the last engagement request.
1125                         //trackingsWithoutExit.EndDate = session.EndDate;
1126                         var lastEngagement = trackingsWithoutExit.MeetingParticipantSession.ParticipantEngagements.OrderByDescending(e => e.DisplayTime)
1127                                                                  .FirstOrDefault();
1128                         if(lastEngagement == null)
1129                             trackingsWithoutExit.EndDate = trackingsWithoutExit.StartDate;
1130                         else
1131                             trackingsWithoutExit.EndDate = lastEngagement.ResponseTime.HasValue
1132                                                                ? lastEngagement.ResponseTime.Value
1133                                                                : lastEngagement.DisplayTime;
1134                     }
1135                     //foreach (
1136                     //    var participantTracking in
1137                     //        participantSession.ParticipantTrackings.Where(t => !t.EndDate.HasValue))
1138                     //    participantTracking.EndDate = session.EndDate;
1139                 }
1140                 context.SubmitChanges();
1141                 CleanupRebroadcastEngagements(session.MeetingSessionKey);
1142                 //CleanupSessionEngagements(session.MeetingSessionKey);
1143             }
1144             //string taskId = Guid.NewGuid().ToString();
1145             //Tasks[taskId] = Task.Factory.StartNew<bool>(() => RunTask(scoId));
1146             //return taskId;
1147         }
1148
1149         //private bool RunTask(int scoId)
1150         //{
1151         //    var result = false;
1152         //    //wait 10 seconds so all user logouts are tracked
1153         //    const int secondsToSleep = 2;
1154         //    Thread.Sleep(secondsToSleep * 1000);
1155         //    try
1156         //    {
1157         //        var context = Database;
1158         //        var session = getLastSession(scoId);
1159         //        if(session!= null)
1160         //        {
1161         //            //session.EndDate = endTime;
1162         //            var participants = context.MeetingParticipantSessions.Where(m => m.MeetingSessionKey == session.MeetingSessionKey);
1163         //            foreach (var participantSession in participants)
1164         //                foreach (var participantTracking in participantSession.ParticipantTrackings.Where(t => !t.EndDate.HasValue))
1165         //                    participantTracking.EndDate = session.EndDate;
1166         //            context.SubmitChanges();
1167         //            //CleanupSessionEngagements(session.MeetingSessionKey);
1168         //            result = true;
1169         //        } 
1170         //    }
1171         //    catch (Exception ex)
1172         //    {
1173         //        Extensions.LogServiceError("CloseArchiveSession", ex);
1174         //        result = false;
1175         //    }
1176         //    return result;
1177         //}
1178         [WebMethod]
1179         public bool CleanupSession(int sessionKey) {
1180             Extensions.LogServiceCall("CleanupSession", string.Format("Parameters: sessionKey = {0}", sessionKey));
1181             CleanupSessionEngagements(sessionKey);
1182             return true;
1183         }
1184
1185         private void CleanupSessionEngagements(int sessionKey) {
1186             try {
1187                 var context = Database;
1188                 Database.ArchiveSessionEngagements(sessionKey);
1189                 var session = context.MeetingSessions.Single(ms => ms.MeetingSessionKey == sessionKey);
1190                 //filter out engagements not sent by host
1191                 var engagementsToKeep = new List<int>();
1192                 var heartbeats = session.MeetingSessionHeartbeats.Select(h => h.Timestamp)
1193                                         .ToList();
1194                 foreach (var heartbeat in heartbeats) {
1195                     foreach (var ps in session.MeetingParticipantSessions) {
1196                         var validEngagements = context.ParticipantEngagements.Where(
1197                                                                                     pe => Math.Abs((pe.DisplayTime - heartbeat).TotalSeconds) < 30.0)
1198                                                       .Select(pe => pe.ParticipantEngagementKey)
1199                                                       .Distinct();
1200                         engagementsToKeep.AddRange(validEngagements);
1201                     }
1202                 }
1203                 engagementsToKeep = engagementsToKeep.Distinct()
1204                                                      .ToList();
1205                 var tooLarge = engagementsToKeep.Count >= 2000;
1206                 var meetingParticipantEngagementKeys = session.MeetingParticipantSessions.Select(mps => mps.MeetingParticipantSessionKey)
1207                                                               .ToList();
1208                 var allParticipantEngagements =
1209                     context.ParticipantEngagements.Where(
1210                                                          pe => meetingParticipantEngagementKeys.Contains(pe.MeetingParticipantSessionKey));
1211                 if(tooLarge) {
1212                     var allEngagementKeys = allParticipantEngagements.Select(pe => pe.ParticipantEngagementKey)
1213                                                                      .ToList();
1214                     var engagementsToDelete = allEngagementKeys.Except(engagementsToKeep);
1215                     var query =
1216                         string.Format(
1217                                       "DELETE FROM dbo.ParticipantEngagements WHERE ParticipantEngagementKey IN({0})",
1218                                       string.Join(",", engagementsToDelete));
1219                     context.ExecuteCommand(query);
1220                 } else {
1221                     var engagementsToDelete =
1222                         allParticipantEngagements.Where(pe => !engagementsToKeep.Contains(pe.ParticipantEngagementKey));
1223                     context.ParticipantEngagements.DeleteAllOnSubmit(engagementsToDelete);
1224                     context.SubmitChanges();
1225                 }
1226                 //filter out duplicates
1227                 var duplicateKeys = new List<int>();
1228                 foreach (var mps in session.MeetingParticipantSessions) {
1229                     var duplicateEngagements = context.GetDuplicateEngagements(mps.MeetingParticipantSessionKey)
1230                                                       .ToList();
1231                     if(duplicateEngagements.Any()) {
1232                         var keysProcessed = new List<int>();
1233                         var keysToKeep = new List<int>();
1234                         foreach (var getDuplicateEngagementsResult in duplicateEngagements) {
1235                             if(!keysProcessed.Contains(getDuplicateEngagementsResult.ParticipantEngagementKey)) {
1236                                 var currentTime = getDuplicateEngagementsResult.DisplayTime;
1237                                 var currentDuplicates =
1238                                     duplicateEngagements.Where(
1239                                                                de => Math.Abs((de.DisplayTime - currentTime).TotalSeconds) <= 30.0);
1240                                 var currentDuplicatesWithResponse = currentDuplicates.Where(cd => cd.ResponseTime.HasValue);
1241                                 var currentDuplicatesWithoutResponse = currentDuplicates.Where(cd => !cd.ResponseTime.HasValue);
1242                                 var dupKeyToKeep = currentDuplicatesWithResponse.Any()
1243                                                        ? currentDuplicatesWithResponse.First()
1244                                                                                       .ParticipantEngagementKey
1245                                                        : currentDuplicatesWithoutResponse.First()
1246                                                                                          .ParticipantEngagementKey;
1247                                 keysToKeep.Add(dupKeyToKeep);
1248                                 keysProcessed.AddRange(currentDuplicates.Select(cd => cd.ParticipantEngagementKey));
1249                             }
1250                         }
1251                         duplicateKeys.AddRange(duplicateEngagements.Where(de => !keysToKeep.Contains(de.ParticipantEngagementKey))
1252                                                                    .Select(de => de.ParticipantEngagementKey));
1253                     }
1254                 }
1255                 if(duplicateKeys.Any()) {
1256                     var distinctKeysToDelete = duplicateKeys.Distinct();
1257                     var duplicates =
1258                         context.ParticipantEngagements.Where(pe => distinctKeysToDelete.Contains(pe.ParticipantEngagementKey));
1259                     context.ParticipantEngagements.DeleteAllOnSubmit(duplicates);
1260                 }
1261                 context.SubmitChanges();
1262             } catch (Exception ex) {
1263                 Extensions.LogServiceError("CleanupSessionEngagements", ex);
1264                 Console.WriteLine(ex.Message);
1265             }
1266         }
1267
1268         [WebMethod]
1269         public bool CleanupRebroadcast(int sessionKey) {
1270             Extensions.LogServiceCall("CleanupRebroadcast", string.Format("Parameters: sessionKey = {0}", sessionKey));
1271             CleanupRebroadcastEngagements(sessionKey);
1272             return true;
1273         }
1274
1275         private void CleanupRebroadcastEngagements(int sessionKey) {
1276             try {
1277                 var context = Database;
1278                 var meetingParticipantSessionKeys = context.MeetingParticipantSessions.Where(mps => mps.MeetingSessionKey == sessionKey)
1279                                                            .Select(mps => mps.MeetingParticipantSessionKey)
1280                                                            .ToArray();
1281                 var archived = context.ParticipantEngagementsArchives.Any(
1282                                                                           pa => meetingParticipantSessionKeys.Contains(pa.MeetingParticipantSessionKey));
1283                 if(!archived)
1284                     Database.ArchiveSessionEngagements(sessionKey);
1285                 var session = context.MeetingSessions.Single(ms => ms.MeetingSessionKey == sessionKey);
1286                 //filter out duplicates
1287                 var duplicateKeys = new List<int>();
1288                 foreach (var mps in session.MeetingParticipantSessions) {
1289                     var duplicateEngagements = context.GetDuplicateEngagements(mps.MeetingParticipantSessionKey)
1290                                                       .ToList();
1291                     if(duplicateEngagements.Any()) {
1292                         var keysProcessed = new List<int>();
1293                         var keysToKeep = new List<int>();
1294                         foreach (var getDuplicateEngagementsResult in duplicateEngagements) {
1295                             if(!keysProcessed.Contains(getDuplicateEngagementsResult.ParticipantEngagementKey)) {
1296                                 var currentTime = getDuplicateEngagementsResult.DisplayTime;
1297                                 var currentDuplicates =
1298                                     duplicateEngagements.Where(
1299                                                                de => Math.Abs((de.DisplayTime - currentTime).TotalSeconds) <= 30.0);
1300                                 var currentDuplicatesWithResponse = currentDuplicates.Where(cd => cd.ResponseTime.HasValue);
1301                                 var currentDuplicatesWithoutResponse = currentDuplicates.Where(cd => !cd.ResponseTime.HasValue);
1302                                 var dupKeyToKeep = currentDuplicatesWithResponse.Any()
1303                                                        ? currentDuplicatesWithResponse.First()
1304                                                                                       .ParticipantEngagementKey
1305                                                        : currentDuplicatesWithoutResponse.First()
1306                                                                                          .ParticipantEngagementKey;
1307                                 keysToKeep.Add(dupKeyToKeep);
1308                                 keysProcessed.AddRange(currentDuplicates.Select(cd => cd.ParticipantEngagementKey));
1309                             }
1310                         }
1311                         duplicateKeys.AddRange(duplicateEngagements.Where(de => !keysToKeep.Contains(de.ParticipantEngagementKey))
1312                                                                    .Select(de => de.ParticipantEngagementKey));
1313                     }
1314                 }
1315                 if(duplicateKeys.Any()) {
1316                     var distinctKeysToDelete = duplicateKeys.Distinct();
1317                     var duplicates =
1318                         context.ParticipantEngagements.Where(pe => distinctKeysToDelete.Contains(pe.ParticipantEngagementKey));
1319                     context.ParticipantEngagements.DeleteAllOnSubmit(duplicates);
1320                 }
1321                 context.SubmitChanges();
1322             } catch (Exception ex) {
1323                 Extensions.LogServiceError("CleanupRebroadcastEngagements", ex);
1324                 Console.WriteLine(ex.Message);
1325             }
1326         }
1327
1328         private void CheckParticipantSessions(MeetingSession session, int principal_id) {
1329             try {
1330                 var context = Database;
1331                 var participantSessionCount =
1332                     session.MeetingParticipantSessions.Count(mps => mps.ParticipantKey == principal_id);
1333                 if(participantSessionCount > 1) {
1334                     var participantSessions =
1335                         session.MeetingParticipantSessions.Where(mps => mps.ParticipantKey == principal_id)
1336                                .OrderBy(
1337                                         mps => mps.MeetingParticipantSessionKey);
1338                     var psToKeep = participantSessions.First();
1339                     var psessionsToDelete =
1340                         participantSessions.Where(
1341                                                   ps => ps.MeetingParticipantSessionKey != psToKeep.MeetingParticipantSessionKey);
1342                     foreach (var meetingParticipantSession in psessionsToDelete) {
1343                         context.MeetingParticipantSessions.DeleteOnSubmit(meetingParticipantSession);
1344                     }
1345                     context.SubmitChanges();
1346                 }
1347             } catch (Exception ex) {
1348                 Extensions.LogServiceError("CheckParticipanSessions", ex);
1349             }
1350         }
1351
1352         private MeetingSession getActiveSessionBySco(int sco_id) {
1353             var now = DateTime.UtcNow;
1354             var session =
1355                 Database.MeetingSessions.FirstOrDefault(
1356                                                         ms => ms.SCO_ID == sco_id && ms.StartDate <= now && (!ms.EndDate.HasValue || ms.EndDate.Value >= now));
1357             return session;
1358         }
1359
1360         private MeetingSession getActiveSessionByKey(int meetingSessionKey) {
1361             var now = DateTime.UtcNow;
1362             var session =
1363                 Database.MeetingSessions.SingleOrDefault(ms => ms.MeetingSessionKey == meetingSessionKey && ms.StartDate <= now);
1364             // Tyler Allen - 09/01/2016
1365             // Just send a session no matter what
1366             // && (!ms.EndDate.HasValue || ms.EndDate.Value >= now));
1367             return session;
1368         }
1369
1370         private MeetingSession getLastSession(int sco_id) {
1371             var now = DateTime.UtcNow;
1372             var session =
1373                 Database.MeetingSessions.Where(
1374                                                ms => ms.SCO_ID == sco_id && ms.StartDate <= now && ms.EndDate.HasValue && ms.EndDate.Value <= now)
1375                         .OrderByDescending(ms => ms.StartDate)
1376                         .FirstOrDefault();
1377             return session;
1378         }
1379
1380         private void CheckParticipantTracking(MeetingParticipantSession participantSession, int principalId) {
1381             //Extensions.LogServiceCall("CheckParticipantTracking", String.Format("Parameters: participantSession.MeetingParticipantSessionKey = {0} principal_id = {1}", participantSession.MeetingParticipantSessionKey, principalId));
1382             var context = Database;
1383             var tracking = context.ParticipantTrackings.Where(t =>
1384                                                               t.Principal_ID == principalId &&
1385                                                               t.MeetingParticipantSessionKey == participantSession.MeetingParticipantSessionKey &&
1386                                                               !t.EndDate.HasValue)
1387                                   .OrderByDescending(t => t.StartDate)
1388                                   .FirstOrDefault();
1389             if(tracking == null) {
1390                 tracking = new ParticipantTracking {
1391                     MeetingParticipantSessionKey =
1392                         participantSession.MeetingParticipantSessionKey,
1393                     Principal_ID = principalId,
1394                     StartDate = DateTime.UtcNow
1395                 };
1396                 context.ParticipantTrackings.InsertOnSubmit(tracking);
1397                 context.SubmitChanges();
1398             }
1399         }
1400     }
1401
1402     public class SessionStatusClass {
1403         public int MeetingSessionKey { get; set; }
1404         public string Status { get; set; }
1405         public int OwnerPrincipalId { get; set; }
1406     }
1407
1408     public class ServiceStatus {
1409         public bool Success { get; set; }
1410         public string Message { get; set; }
1411     }
1412
1413     public class Setting {
1414         public int Interval { get; set; }
1415         public string Text { get; set; }
1416         public int Duration { get; set; }
1417     }
1418
1419     public class Detail {
1420         public string CourseCode { get; set; }
1421         public string Instructor { get; set; }
1422         public string Location { get; set; }
1423         public string TopicID { get; set; }
1424         public string Topic { get; set; }
1425     }
1426 }