Beispiel #1
0
 public NotifyEventArgs(NotifyState AState, int AFirst, int ALast, bool AUpdate)
 {
     this.State        = AState;
     this.FirstChanged = AFirst;
     this.LastChanged  = ALast;
     this.Update       = AUpdate;
 }
        // When the NotifyState property changes, perform the relevant logic on the notification TabControl
        private static void NotifyStatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            NotifyState         oldState = (NotifyState)e.OldValue;
            NotifyState         state    = (NotifyState)e.NewValue;
            NotificationControl control  = sender as NotificationControl;

            if (control == null)
            {
                return;
            }
            switch (state)
            {
            case NotifyState.Collapsed:
                control.NotifyCollapse(oldState);
                break;

            case NotifyState.Docked:
                control.NotifyDock(oldState);
                break;

            case NotifyState.Visible:
                control.NotifyVisible(oldState);
                break;
            }
        }
Beispiel #3
0
        /// <summary>
        /// 判断用户的通知设置
        /// </summary>
        /// <param name="userID">用户</param>
        /// <param name="notifyType">类型</param>
        /// <returns></returns>
        private bool CheckUserNotifySettings(int userID, int notifyType)
        {
            NotifyState SysState = AllSettings.Current.NotifySettings.GetNotifySystemState(notifyType);

            SystemNotifyProvider.Update();

            //判断系统设置
            switch (SysState)
            {
            case NotifyState.AlwaysClose:
                return(false);

            case NotifyState.DefaultClose:
            case NotifyState.DefaultOpen:
                //判断用户设置
                UserNotifySetting userSetting = UserBO.Instance.GetNotifySetting(userID);
                if (userSetting != null && userSetting.GetNotifyState(notifyType) == NotifyState.DefaultClose)
                {
                    return(false);
                }
                break;
            }

            return(true);
        }
Beispiel #4
0
 void EnterState(NotifyState ns)
 {
     if (ns != currs)
     {
         OnLeave(currs);
         InitState(ns);
     }
 }
Beispiel #5
0
 private void    OnEnter(NotifyState ns)
 {
     switch (ns)
     {
     case NotifyState.eWidth:
         obj.SetHint(true);
         break;
     }
 }
Beispiel #6
0
 private void OnLeave(NotifyState ns)
 {
     switch (ns)
     {
     case NotifyState.eFail:
         obj.SetHint(false);
         break;
     }
 }
        /// <summary>
        /// Depending on NotifyState, translate the passed height
        /// </summary>
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length != 2 || !(values[0] is double) || !(values[1] is NotifyState))
            {
                return(0);
            }
            double      height = (double)values[0];
            NotifyState state  = (NotifyState)values[1];

            return(NotificationControl.GetHeight(height, state));
        }
 // helper method to collapse the notification window
 private void NotifyCollapse(NotifyState oldValue)
 {
     if (oldValue == NotifyState.Visible)
     {
         layer1.Visibility = Visibility.Collapsed;
     }
     else if (oldValue == NotifyState.Docked)
     {
         NotifyVisible(oldValue);
         layer1.Visibility = Visibility.Collapsed;
     }
 }
 /// <summary>
 /// Helper method to get height based on the current state of the notification window.
 /// When state is Docked, then use SplitterHeight, else use smaller 5.0
 /// </summary>
 /// <param name="height"></param>
 /// <param name="state"></param>
 /// <returns></returns>
 internal static double GetHeight(double height, NotifyState state)
 {
     if (state == NotifyState.Docked)
     {
         return(height);
     }
     else
     {
         // reduce the splitter height
         return(5.0);
     }
 }
 // helper method to dock the notification window
 private void NotifyDock(NotifyState oldValue)
 {
     if (oldValue == NotifyState.Collapsed)
     {
         NotifyVisible(oldValue);
     }
     layer0.RowDefinitions.Add(row1CloneForLayer0);
     layer0.Margin = new Thickness(0, 0, 0, -1);
     bottomPanel.Children.Remove(tabPanel);
     border.Child = tabPanel;
     // remove the rotation in the image
     pinImage.RenderTransform = null;
     // add (arbitrary not null) Tag to gridSplitter so that style trigger displays the splitter
     gridSplitter.Tag = gridSplitter;
 }
 // helper method to make visible the notification window
 private void NotifyVisible(NotifyState oldValue)
 {
     if (oldValue == NotifyState.Docked)
     {
         layer0.RowDefinitions.Remove(row1CloneForLayer0);
         layer0.Margin = new Thickness(0, 0, 0, 2);
         border.Child  = null;
         bottomPanel.Children.Add(tabPanel);
         RotateImage(pinImage);
         // remove Tag from gridSplitter so that style trigger displays the splitter
         gridSplitter.Tag = null;
     }
     else if (oldValue == NotifyState.Collapsed)
     {
         layer1.Visibility = Visibility.Visible;
     }
 }
Beispiel #12
0
        public UserNotifySetting(string values)
        {
            this.AllNotify = new NotifySettingItemCollection();
            foreach (NotifyType nt in NotifyBO.AllNotifyTypes)
            {
                NotifySettingItem item = new NotifySettingItem();
                item.NotifyType = nt.TypeID;
                item.OpenState  = AllSettings.Current.NotifySettings.GetNotifySystemState(item.NotifyType);
                this.AllNotify.Add(item);
            }


            while (!string.IsNullOrEmpty(values) && values.Length % 4 == 0 && values.Length >= 4)
            {
                int value = 0;
                try
                {
                    value = int.Parse(values.Substring(0, 4));
                }
                catch
                {
                    return;
                }
                int         type  = (int)(value / 10);
                NotifyState state = (NotifyState)(value % 10);

                foreach (NotifySettingItem item in this.AllNotify)
                {
                    if (item.OpenState == NotifyState.AlwaysOpen || item.OpenState == NotifyState.AlwaysClose)
                    {
                        continue;
                    }
                    if (item.NotifyType == type)
                    {
                        item.OpenState = state;
                    }
                }
                values = values.Substring(4);
            }
        }
 private void OnSendEMailLogComplete(MailUserToken userToken, NotifyState notifyState)
 {
     if (userToken == null) return;
     NotifyContent ntfContext = userToken.NtfContext;
     ntfContext.NotifyState = notifyState;
     bool res = MonitorDataAccessor.Instance().InsertSendEmailOprLog(DateTime.Now.ToString("yyyy-MM-dd"),
       CommandTextParser.GetJsonSerialization<NotifyContent>(ntfContext),
       SystemHelper.GetUtcTicksByDateTime(DateTime.Now));
     //写日志
 }
 private void OnSendEMailLogComplete(MailUserToken userToken,NotifyState notifyState)
 {
     if (this.WriteSendEMailLogEvent != null)
     {
         this.WriteSendEMailLogEvent.Invoke(userToken, notifyState);
     }
 }
Beispiel #15
0
        // Events that we want to update as soon as possible.  Return next time this should be called.
        private int SendFastRateEvents()
        {
            if (reset_notify_state_)
            {
                notify_state_ = new NotifyState();
            }
            reset_notify_state_ = false;

            bool game_exists = ffxiv_.FindProcess();

            if (game_exists != notify_state_.game_exists)
            {
                notify_state_.game_exists = game_exists;
                DispatchToJS(new JSEvents.GameExistsEvent(game_exists));
            }

            bool game_active = game_active = ffxiv_.IsActive();

            if (game_active != notify_state_.game_active)
            {
                notify_state_.game_active = game_active;
                DispatchToJS(new JSEvents.GameActiveChangedEvent(game_active));
            }

            // Silently stop sending other messages if the ffxiv process isn't around.
            if (!game_exists)
            {
                return(kUberSlowTimerMilli);
            }

            // onInCombatChangedEvent: Fires when entering or leaving combat.
            bool in_act_combat  = ActGlobals.oFormActMain.InCombat;
            bool in_game_combat = ffxiv_.GetInGameCombat();

            if (!notify_state_.in_act_combat.HasValue || in_act_combat != notify_state_.in_act_combat ||
                !notify_state_.in_game_combat.HasValue || in_game_combat != notify_state_.in_game_combat)
            {
                notify_state_.in_act_combat  = in_act_combat;
                notify_state_.in_game_combat = in_game_combat;
                DispatchToJS(new JSEvents.InCombatChangedEvent(in_act_combat, in_game_combat));
            }

            // onZoneChangedEvent: Fires when the player changes their current zone.
            string zone_name = ActGlobals.oFormActMain.CurrentZone;

            if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name))
            {
                notify_state_.zone_name = zone_name;
                DispatchToJS(new JSEvents.ZoneChangedEvent(zone_name));
                ClearFateWatcherDictionaries();
            }

            DateTime now = DateTime.Now;

            // The |player| can be null, such as during a zone change.
            FFXIVProcess.EntityData player = ffxiv_.GetSelfData();

            // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
            // lost.
            if (player != null)
            {
                bool dead = player.hp == 0;
                if (dead != notify_state_.dead)
                {
                    notify_state_.dead = dead;
                    if (dead)
                    {
                        DispatchToJS(new JSEvents.PlayerDiedEvent());
                    }
                }
            }

            // onPlayerChangedEvent: Fires when current player data changes.
            if (player != null)
            {
                bool send = false;
                if (!player.Equals(notify_state_.player))
                {
                    // Clear the FATE dictionary if we switched characters
                    if (notify_state_.player != null && !player.name.Equals(notify_state_.player.name))
                    {
                        ClearFateWatcherDictionaries();
                    }
                    notify_state_.player = player;
                    send = true;
                }
                var job = ffxiv_.GetJobSpecificData(player.job);
                if (job != null)
                {
                    if (send || !JObject.DeepEquals(job, notify_state_.job_data))
                    {
                        notify_state_.job_data = job;
                        var ev = new JSEvents.PlayerChangedEvent(player);
                        ev.jobDetail = job;
                        DispatchToJS(ev);
                    }
                }
                else if (send)
                {
                    // No job-specific data.
                    DispatchToJS(new JSEvents.PlayerChangedEvent(player));
                }
            }

            // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
            // more specific events, some of which may involve parsing the logs as well.
            List <string> logs;
            List <string> import_logs;

            log_lines_semaphore_.Wait();
            logs              = log_lines_;
            log_lines_        = last_log_lines_;
            import_logs       = import_log_lines_;
            import_log_lines_ = last_import_log_lines_;

            log_lines_semaphore_.Release();

            if (logs.Count > 0)
            {
                DispatchToJS(new JSEvents.LogEvent(logs));
                logs.Clear();
            }

            last_log_lines_        = logs;
            last_import_log_lines_ = import_logs;

            return(game_active ? kFastTimerMilli : kSlowTimerMilli);
        }
Beispiel #16
0
        public bool AddNotify(AuthUser oprateUser, Notify notify)
        {
            #region 基础参数检查

            if (notify == null)
            {
                return(false);
            }


            if (notify.UserID <= 0)
            {
                return(false);
            }

            if (notify.UserID == oprateUser.UserID)
            {
                return(true);
            }

            #endregion

            UnreadNotifies UnreadNotifies = null;
#if !Passport
            PassportClientConfig setting = Globals.PassportClient;

            if (setting.EnablePassport)
            {
                NotifyType type = AllNotifyTypes[notify.TypeID];


                NotifyActionProxy[] proxys = new NotifyActionProxy[notify.Actions.Count];
                int i = 0;
                foreach (NotifyAction action in notify.Actions)
                {
                    NotifyActionProxy nap = new NotifyActionProxy();
                    nap.Url      = "{clienturl}" + action.Url;
                    nap.Title    = action.Title;
                    nap.IsDialog = action.IsDialog;
                    proxys[i]    = nap;
                    i++;
                }

                ThreadPool.QueueUserWorkItem(delegate(object a) {
                    try
                    {
                        setting.PassportService.Notify_Send(notify.UserID, type.TypeName, notify.Content, notify.DataTable.ToString(), proxys, notify.Keyword);
                    }
                    catch
                    {
                    }
                });
            }
            else
#endif
            {
                NotifyState SysState = AllSettings.Current.NotifySettings.GetNotifySystemState(notify.TypeID);
                SystemNotifyProvider.Update();

                //判断系统设置
                switch (SysState)
                {
                case NotifyState.AlwaysClose:
                    return(false);

                case NotifyState.DefaultClose:
                case NotifyState.DefaultOpen:
                    //判断用户设置
                    UserNotifySetting userSetting = UserBO.Instance.GetNotifySetting(notify.UserID);
                    if (userSetting != null && userSetting.GetNotifyState(notify.TypeID) == NotifyState.DefaultClose)
                    {
                        return(false);
                    }
                    break;
                }

                StringTable actions = new StringTable();
                if (notify.Actions != null)
                {
                    foreach (NotifyAction na in notify.Actions)
                    {
                        actions.Add(na.Title, (na.IsDialog ? "*" : "") + na.Url);
                    }
                }
                NotifyDao.Instance.AddNotify(notify.UserID, notify.TypeID, notify.Content, notify.Keyword, notify.DataTable.ToString(), 0, actions.ToString(), out UnreadNotifies);

                AuthUser user;
                user = UserBO.Instance.GetUserFromCache <AuthUser>(notify.UserID);
                if (user != null)
                {
                    user.UnreadNotify = UnreadNotifies;
                }

                if (OnUserNotifyCountChanged != null)
                {
                    OnUserNotifyCountChanged(UnreadNotifies);
                }

                RemoveCacheByType(notify.UserID, 0);
                return(true);
            }

            return(true);
        }
Beispiel #17
0
    // Events that we want to update as soon as possible.
    private void SendFastRateEvents() {
      // Hold this while we're in here to prevent the Renderer or Browser from disappearing from under us.
      fast_update_timer_semaphore_.Wait();

      // Handle startup and shutdown. And do not fire any events until the page has loaded and had a chance to
      // register its event handlers.
      if (Overlay == null || Overlay.Renderer == null || Overlay.Renderer.Browser == null || Overlay.Renderer.Browser.IsLoading) {
        fast_update_timer_semaphore_.Release();
        fast_update_timer_.Interval = kSlowTimerMilli;
        fast_update_timer_.Start();
        return;
      }

      if (reset_notify_state_)
        notify_state_ = new NotifyState();
      reset_notify_state_ = false;

      if (!notify_state_.sent_data_dir && Config.Url.Length > 0) {
        notify_state_.sent_data_dir = true;

        var web = new System.Net.WebClient();

        var data_file_paths = new List<string>();
        try {
          var data_dir_manifest = new Uri(new Uri(Config.Url), "data/manifest.txt");
          var manifest_reader = new StringReader(web.DownloadString(data_dir_manifest));
          for (var line = manifest_reader.ReadLine(); line != null; line = manifest_reader.ReadLine()) {
            line = line.Trim();
            if (line.Length > 0)
              data_file_paths.Add(line);
          }
        } catch (System.Net.WebException e) {
          if (e.Status == System.Net.WebExceptionStatus.ProtocolError &&
              e.Response is System.Net.HttpWebResponse &&
              ((System.Net.HttpWebResponse)e.Response).StatusCode == System.Net.HttpStatusCode.NotFound) {
            // Ignore file not found.
          } else if (e.InnerException != null &&
            (e.InnerException is FileNotFoundException || e.InnerException is DirectoryNotFoundException)) {
            // Ignore file not found.
          } else if (e.InnerException != null && e.InnerException.InnerException != null &&
            (e.InnerException.InnerException is FileNotFoundException || e.InnerException.InnerException is DirectoryNotFoundException)) {
            // Ignore file not found.
          } else {
            LogError("Unable to read manifest file: " + e.Message);
          }
        } catch (Exception e) {
          LogError("Unable to read manifest file: " + e.Message);
        }

        if (data_file_paths.Count > 0) {
          var file_data = new Dictionary<string, string>();
          foreach (string data_filename in data_file_paths) {
            try {
              var file_path = new Uri(new Uri(Config.Url), "data/" + data_filename);
              var file_reader = new StringReader(web.DownloadString(file_path));
              file_data[data_filename] = file_reader.ReadToEnd();
            } catch (Exception e) {
              LogError("Unable to read data file: " + e.Message);
            }
          }
          OnDataFilesRead(new JSEvents.DataFilesRead(file_data));
        }
      }

      bool game_exists = ffxiv_.FindProcess();
      if (game_exists != notify_state_.game_exists) {
        notify_state_.game_exists = game_exists;
        OnGameExists(new JSEvents.GameExistsEvent(game_exists));
      }

      bool game_active = game_active = ffxiv_.IsActive();
      if (game_active != notify_state_.game_active) {
        notify_state_.game_active = game_active;
        OnGameActiveChanged(new JSEvents.GameActiveChangedEvent(game_active));
      }

      // Silently stop sending other messages if the ffxiv process isn't around.
      if (!game_exists) {
        fast_update_timer_semaphore_.Release();
        fast_update_timer_.Interval = kUberSlowTimerMilli;
        fast_update_timer_.Start();
        return;
      }

      // onInCombatChangedEvent: Fires when entering or leaving combat.
      bool in_combat = FFXIV_ACT_Plugin.ACTWrapper.InCombat;
      if (!notify_state_.in_combat.HasValue || in_combat != notify_state_.in_combat) {
        notify_state_.in_combat = in_combat;
        OnInCombatChanged(new JSEvents.InCombatChangedEvent(in_combat));
      }

      // onZoneChangedEvent: Fires when the player changes their current zone.
      string zone_name = FFXIV_ACT_Plugin.ACTWrapper.CurrentZone;
      if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name)) {
        notify_state_.zone_name = zone_name;
        OnZoneChanged(new JSEvents.ZoneChangedEvent(zone_name));
      }

      DateTime now = DateTime.Now;
      // The |player| can be null, such as during a zone change.
      Tuple<FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> player_data = ffxiv_.GetSelfData();
      var player = player_data != null ? player_data.Item1 : null;
      var player_cast = player_data != null ? player_data.Item2 : null;
      // The |target| can be null when no target is selected.
      Tuple<FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> target_data = ffxiv_.GetTargetData();
      var target = target_data != null ? target_data.Item1 : null;
      var target_cast = target_data != null ? target_data.Item2 : null;
      // The |focus| can be null when no focus target is selected.
      Tuple<FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> focus_data = ffxiv_.GetFocusData();
      var focus = focus_data != null ? focus_data.Item1 : null;
      var focus_cast = focus_data != null ? focus_data.Item2 : null;

      // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
      // lost.
      if (player != null) {
        bool dead = player.hp == 0;
        if (dead != notify_state_.dead) {
          notify_state_.dead = dead;
          if (dead)
            OnPlayerDied(new JSEvents.PlayerDiedEvent());
        }
      }

      // onPlayerChangedEvent: Fires when current player data changes.
      if (player != null) {
        bool send = false;
        if (player != notify_state_.player) {
          notify_state_.player = player;
          send = true;
        }

        if (player.job == FFXIVProcess.EntityJob.RDM) {
          var job = ffxiv_.GetRedMage();
          if (job != null) {
            if (send || !job.Equals(notify_state_.rdm)) {
              notify_state_.rdm = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.RedMageDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.WAR) {
          var job = ffxiv_.GetWarrior();
          if (job != null) {
            if (send || !job.Equals(notify_state_.war)) {
              notify_state_.war = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.WarriorDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.DRK) {
          var job = ffxiv_.GetDarkKnight();
          if (job != null) {
            if (send || !job.Equals(notify_state_.drk)) {
              notify_state_.drk = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.DarkKnightDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.PLD) {
          var job = ffxiv_.GetPaladin();
          if (job != null) {
            if (send || !job.Equals(notify_state_.pld)) {
              notify_state_.pld = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.PaladinDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.BRD) {
          var job = ffxiv_.GetBard();
          if (job != null) {
            if (send || !job.Equals(notify_state_.brd)) {
              notify_state_.brd = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.BardDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.NIN) {
          var job = ffxiv_.GetNinja();
          if (job != null) {
            if (send || !job.Equals(notify_state_.nin)) {
              notify_state_.nin = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.NinjaDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.BLM || player.job == FFXIVProcess.EntityJob.THM) {
          var job = ffxiv_.GetBlackMage();
          if (job != null) {
            if (send || !job.Equals(notify_state_.blm)) {
              notify_state_.blm = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.BlackMageDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.WHM) {
          var job = ffxiv_.GetWhiteMage();
          if (job != null) {
            if (send || !job.Equals(notify_state_.whm)) {
              notify_state_.whm = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.WhiteMageDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.SMN || player.job == FFXIVProcess.EntityJob.SCH || player.job == FFXIVProcess.EntityJob.ACN) {
          var job = ffxiv_.GetSummonerAndScholar();
          if (job != null) {
            if (send || !job.Equals(notify_state_.smn_sch)) {
              notify_state_.smn_sch = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.SummonerAndScholarDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.MNK || player.job == FFXIVProcess.EntityJob.PGL) {
          var job = ffxiv_.GetMonk();
          if (job != null) {
            if (send || !job.Equals(notify_state_.mnk)) {
              notify_state_.mnk = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.MonkDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.MCH) {
          var job = ffxiv_.GetMachinist();
          if (job != null) {
            if (send || !job.Equals(notify_state_.mch)) {
              notify_state_.mch = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.MachinistDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (player.job == FFXIVProcess.EntityJob.AST) {
          var job = ffxiv_.GetAstrologian();
          if (job != null) {
            if (send || !job.Equals(notify_state_.ast)) {
              notify_state_.ast = job;
              var e = new JSEvents.PlayerChangedEvent(player);
              e.jobDetail = new JSEvents.PlayerChangedEvent.AstrologianDetail(job);
              OnPlayerChanged(e);
            }
          }
        } else if (send) {
          // TODO: SAM everything
          // No job-specific data.
          OnPlayerChanged(new JSEvents.PlayerChangedEvent(player));
        }
      }

      // onTargetChangedEvent: Fires when current target or their state changes.
      if (target != notify_state_.target) {
        notify_state_.target = target;
        if (target != null)
          OnTargetChanged(new JSEvents.TargetChangedEvent(target));
        else
          OnTargetChanged(new JSEvents.TargetChangedEvent(null));
      }

      // onTargetCastingEvent: Fires each tick while the target is casting, and once
      // with null when not casting.
      int target_cast_id = target_cast != null ? target_cast.casting_id : 0;
      if (target_cast_id != 0 || target_cast_id != notify_state_.target_cast_id) {
        notify_state_.target_cast_id = target_cast_id;
        // The game considers things to be casting once progress reaches the end for a while, as the server is
        // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
        // be casting anymore once it reaches the end.
        if (target_cast_id != 0 && target_cast.casting_time_progress < target_cast.casting_time_length) {
          DateTime start = now.AddSeconds(-target_cast.casting_time_progress);
          // If the start is within the timer interval, assume it's the same cast. Since we sample the game
          // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
          // helps avoid it.
          TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
          if (start + range < notify_state_.target_cast_start || start - range > notify_state_.target_cast_start)
            notify_state_.target_cast_start = start;
          TimeSpan progress = now - notify_state_.target_cast_start;
          OnTargetCasting(new JSEvents.TargetCastingEvent(target_cast.casting_id, progress.TotalSeconds, target_cast.casting_time_length));
        } else {
          notify_state_.target_cast_start = new DateTime();
          OnTargetCasting(new JSEvents.TargetCastingEvent(0, 0, 0));
        }
      }

      // onFocusChangedEvent: Fires when current focus target or their state changes.
      if (focus != notify_state_.focus) {
        notify_state_.focus = focus;
        if (target != null)
          OnFocusChanged(new JSEvents.FocusChangedEvent(focus));
        else
          OnFocusChanged(new JSEvents.FocusChangedEvent(null));
      }

      // onFocusCastingEvent: Fires each tick while the focus target is casting, and
      // once with null when not casting.
      int focus_cast_id = focus_cast != null ? focus_cast.casting_id : 0;
      if (focus_cast_id != 0 || focus_cast_id != notify_state_.focus_cast_id) {
        notify_state_.focus_cast_id = focus_cast_id;
        // The game considers things to be casting once progress reaches the end for a while, as the server is
        // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
        // be casting anymore once it reaches the end.
        if (focus_cast_id != 0 && focus_cast.casting_time_progress < focus_cast.casting_time_length) {
          DateTime start = now.AddSeconds(-focus_cast.casting_time_progress);
          // If the start is within the timer interval, assume it's the same cast. Since we sample the game
          // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
          // helps avoid it.
          TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
          if (start + range < notify_state_.focus_cast_start || start - range > notify_state_.focus_cast_start)
            notify_state_.focus_cast_start = start;
          TimeSpan progress = now - notify_state_.focus_cast_start;
          OnFocusCasting(new JSEvents.FocusCastingEvent(focus_cast.casting_id, progress.TotalSeconds, focus_cast.casting_time_length));
        } else {
          notify_state_.focus_cast_start = new DateTime();
          OnFocusCasting(new JSEvents.FocusCastingEvent(0, 0, 0));
        }
      }

      // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
      // more specific events, some of which may involve parsing the logs as well.
      List<string> logs;
      log_lines_semaphore_.Wait();
      logs = log_lines_;
      log_lines_ = last_log_lines_;
      log_lines_semaphore_.Release();
      if (logs.Count > 0) {
        OnLogsChanged(new JSEvents.LogEvent(logs));
        logs.Clear();
      }
      last_log_lines_ = logs;

      fight_tracker_.Tick(DateTime.Now);

      fast_update_timer_semaphore_.Release();
      fast_update_timer_.Interval = game_active ? kFastTimerMilli : kSlowTimerMilli;
      fast_update_timer_.Start();
    }
        public async Task NotifyIfConnected <TEntity>(string relatedUserSub, TEntity user, NotifyState state)
            where TEntity : class, IUser
        {
            string connectionId = UserConnections.Connections.GetValueOrDefault(relatedUserSub);

            if (connectionId != null)
            {
                await dataHub.Clients.Client(connectionId)
                .SendCoreAsync(state.ToString(), new object[] { user.UserName });
            }
        }
Beispiel #19
0
 void    InitState(NotifyState ns)
 {
     currs     = ns;
     starttime = Time.time;
     OnEnter(currs);
 }
Beispiel #20
0
        // Events that we want to update as soon as possible.  Return next time this should be called.
        private int SendFastRateEvents()
        {
            if (reset_notify_state_)
            {
                notify_state_ = new NotifyState();
            }
            reset_notify_state_ = false;

            // Loading dance:
            // * OverlayPlugin loads addons and initializes event sources.
            // * OverlayPlugin loads its configuration.
            // * Event sources are told to load their configuration and start (LoadConfig and Start are called).
            // * Overlays are initialised and the browser instances are started. At this points the overlays start loading.
            // * At some point the overlay's JavaScript is executed and OverlayPluginApi is injected. This order isn't
            //   deterministic and depends on what the ACT process is doing at that point in time. During startup the
            //   OverlayPluginApi is usually injected after the overlay is done loading while an overlay that's reloaded or
            //   loaded later on will see the OverlayPluginApi before the page has loaded.
            // * The overlay JavaScript sets up the initial event handlers and calls the cactbotLoadUser handler through
            //   getUserConfigLocation. These actions are queued by the JS implementation in common.js until OverlayPluginApi
            //   (or the WebSocket) is available. Once it is, the event subscriptions and handler calls are transmitted.
            // * OverlayPlugin stores the event subscriptions and executes the C# handlers which in this case means
            //   FetchUserFiles is called. That method loads the user files and returns them. The result is now transmitted
            //   back to the overlay that called the handler and the Promise in JS is resolved with the result.
            // * getUserConfigLocation processes the received information and calls the passed callback. This constructs the
            //   overlay specific objects and registers additional event handlers. Finally, the cactbotRequestState handler
            //   is called.
            // * OverlayPlugin processes the new event subscriptions and executes the cactbotRequestState handler.
            // * The next time SendFastRateEvents is called, it resets notify_state_ (since the previous handler set
            //   reset_notify_state_ to true) which causes it to dispatch all state events again. These events are now
            //   dispatched to all subscribed overlays. However, this means that overlays can receive state events multiple
            //   times during startup. If the user has three Cactbot overlays, all of them will call cactbotRequestState and
            //   thus cause this to happen one to three times depending on their timing. This shouldn't cause any issues but
            //   it's a waste of CPU cycles.
            // * Since this only happens during startup, it's probably not worth fixing though. Not sure.
            // * Some overlays behave slightly different from the above explanation. Raidboss for example loads data files
            //   in addition to the listed steps. I think it's even loading them twice since raidboss.js loads the data files
            //   for gTimelineController and popup-text.js requests them again for its own purposes.

            bool game_exists = ffxiv_.FindProcess();

            if (game_exists != notify_state_.game_exists)
            {
                notify_state_.game_exists = game_exists;
                OnGameExists(new JSEvents.GameExistsEvent(game_exists));
            }

            bool game_active = game_active = ffxiv_.IsActive();

            if (game_active != notify_state_.game_active)
            {
                notify_state_.game_active = game_active;
                OnGameActiveChanged(new JSEvents.GameActiveChangedEvent(game_active));
            }

            // Silently stop sending other messages if the ffxiv process isn't around.
            if (!game_exists)
            {
                return(kUberSlowTimerMilli);
            }

            // onInCombatChangedEvent: Fires when entering or leaving combat.
            bool in_act_combat  = Advanced_Combat_Tracker.ActGlobals.oFormActMain.InCombat;
            bool in_game_combat = ffxiv_.GetInGameCombat();

            if (!notify_state_.in_act_combat.HasValue || in_act_combat != notify_state_.in_act_combat ||
                !notify_state_.in_game_combat.HasValue || in_game_combat != notify_state_.in_game_combat)
            {
                notify_state_.in_act_combat  = in_act_combat;
                notify_state_.in_game_combat = in_game_combat;
                OnInCombatChanged(new JSEvents.InCombatChangedEvent(in_act_combat, in_game_combat));
            }

            // onZoneChangedEvent: Fires when the player changes their current zone.
            string zone_name = Advanced_Combat_Tracker.ActGlobals.oFormActMain.CurrentZone;

            if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name))
            {
                notify_state_.zone_name = zone_name;
                OnZoneChanged(new JSEvents.ZoneChangedEvent(zone_name));
                ClearFateWatcherDictionaries();
            }

            DateTime now = DateTime.Now;

            // The |player| can be null, such as during a zone change.
            FFXIVProcess.EntityData player = ffxiv_.GetSelfData();

            // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
            // lost.
            if (player != null)
            {
                bool dead = player.hp == 0;
                if (dead != notify_state_.dead)
                {
                    notify_state_.dead = dead;
                    if (dead)
                    {
                        OnPlayerDied(new JSEvents.PlayerDiedEvent());
                    }
                }
            }

            // onPlayerChangedEvent: Fires when current player data changes.
            if (player != null)
            {
                bool send = false;
                if (!player.Equals(notify_state_.player))
                {
                    // Clear the FateWatcher dictionaries if we switched characters
                    if (notify_state_.player != null && !player.name.Equals(notify_state_.player.name))
                    {
                        ClearFateWatcherDictionaries();
                    }
                    notify_state_.player = player;
                    send = true;
                }
                var job = ffxiv_.GetJobSpecificData(player.job);
                if (job != null)
                {
                    if (send || !JObject.DeepEquals(job, notify_state_.job_data))
                    {
                        notify_state_.job_data = job;
                        var e = new JSEvents.PlayerChangedEvent(player);
                        e.jobDetail = job;
                        OnPlayerChanged(e);
                    }
                }
                else if (send)
                {
                    // No job-specific data.
                    OnPlayerChanged(new JSEvents.PlayerChangedEvent(player));
                }
            }

            // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
            // more specific events, some of which may involve parsing the logs as well.
            List <string> logs;
            List <string> import_logs;

            log_lines_semaphore_.Wait();
            logs              = log_lines_;
            log_lines_        = last_log_lines_;
            import_logs       = import_log_lines_;
            import_log_lines_ = last_import_log_lines_;
            log_lines_semaphore_.Release();

            if (logs.Count > 0)
            {
                OnLogsChanged(new JSEvents.LogEvent(logs));
                logs.Clear();
            }
            if (import_logs.Count > 0)
            {
                OnImportLogsChanged(new JSEvents.ImportLogEvent(import_logs));
                import_logs.Clear();
            }

            last_log_lines_        = logs;
            last_import_log_lines_ = import_logs;

            return(game_active ? kFastTimerMilli : kSlowTimerMilli);
        }
        // Events that we want to update as soon as possible.  Return next time this should be called.
        private int SendFastRateEvents()
        {
            // Handle startup and shutdown. And do not fire any events until the page has loaded and had a chance to
            // register its event handlers.
            if (Overlay == null || Overlay.Renderer == null || Overlay.Renderer.Browser == null || Overlay.Renderer.Browser.IsLoading)
            {
                return(kSlowTimerMilli);
            }

            if (reset_notify_state_)
            {
                notify_state_ = new NotifyState();
            }
            reset_notify_state_ = false;

            // Loading dance:
            // * wait for !CefBrowser::IsLoading.
            // * Execute JS in all overlays to wait for DOMContentReady and send back an event.
            // * When this OverlayMessage comes in, send back an onInitializeOverlay message.
            // * Overlays should load options from the provider user data dir in that message.
            // * As DOMContentReady happened, overlays should also construct themselves and attach to the DOM.
            // * Now, messages can be sent freely and everything is initialized.
            if (!notify_state_.added_dom_content_listener)
            {
                // Send this from C# so that overlays that are using non-cactbot html
                // (e.g. dps overlays) don't have to be modified.
                const string waitForDOMContentReady = @"
          (function() {
            var sendDOMContentLoaded = function() {
              if (!window.OverlayPluginApi) {
                window.setTimeout(sendDOMContentLoaded, 100);
              } else {
                window.OverlayPluginApi.overlayMessage(OverlayPluginApi.overlayName, JSON.stringify({'onDOMContentLoaded': true}));
              }
            };
            if (document.readyState == 'loaded' || document.readyState == 'complete') {
              sendDOMContentLoaded();
            } else {
              document.addEventListener('DOMContentLoaded', sendDOMContentLoaded);
            }
          })();
        ";
                this.Overlay.Renderer.ExecuteScript(waitForDOMContentReady);
                notify_state_.added_dom_content_listener = true;
            }

            // This flag set as a result of onDOMContentLoaded overlay message.
            if (!notify_state_.dom_content_loaded)
            {
                return(kSlowTimerMilli);
            }

            if (!notify_state_.sent_data_dir && Config.Url.Length > 0)
            {
                notify_state_.sent_data_dir = true;

                var url = Config.Url;
                // If file is a remote pointer, load that file explicitly so that the manifest
                // is relative to the pointed to url and not the local file.
                if (url.StartsWith("file:///"))
                {
                    var html  = File.ReadAllText(new Uri(url).LocalPath);
                    var match = System.Text.RegularExpressions.Regex.Match(html, @"<meta http-equiv=""refresh"" content=""0; url=(.*)?""\/?>");
                    if (match.Groups.Count > 1)
                    {
                        url = match.Groups[1].Value;
                    }
                }

                var web = new System.Net.WebClient();
                web.Encoding = System.Text.Encoding.UTF8;
                System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Ssl3 | System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;

                var data_file_paths = new List <string>();
                try {
                    var data_dir_manifest = new Uri(new Uri(url), "data/manifest.txt");
                    var manifest_reader   = new StringReader(web.DownloadString(data_dir_manifest));
                    for (var line = manifest_reader.ReadLine(); line != null; line = manifest_reader.ReadLine())
                    {
                        line = line.Trim();
                        if (line.Length > 0)
                        {
                            data_file_paths.Add(line);
                        }
                    }
                } catch (System.Net.WebException e) {
                    if (e.Status == System.Net.WebExceptionStatus.ProtocolError &&
                        e.Response is System.Net.HttpWebResponse &&
                        ((System.Net.HttpWebResponse)e.Response).StatusCode == System.Net.HttpStatusCode.NotFound)
                    {
                        // Ignore file not found.
                    }
                    else if (e.InnerException != null &&
                             (e.InnerException is FileNotFoundException || e.InnerException is DirectoryNotFoundException))
                    {
                        // Ignore file not found.
                    }
                    else if (e.InnerException != null && e.InnerException.InnerException != null &&
                             (e.InnerException.InnerException is FileNotFoundException || e.InnerException.InnerException is DirectoryNotFoundException))
                    {
                        // Ignore file not found.
                    }
                    else
                    {
                        LogError("Unable to read manifest file: " + e.Message);
                    }
                } catch (Exception e) {
                    LogError("Unable to read manifest file: " + e.Message);
                }

                if (data_file_paths.Count > 0)
                {
                    var file_data = new Dictionary <string, string>();
                    foreach (string data_filename in data_file_paths)
                    {
                        try {
                            var file_path = new Uri(new Uri(url), "data/" + data_filename);
                            file_data[data_filename] = web.DownloadString(file_path);
                        } catch (Exception e) {
                            LogError("Unable to read data file: " + e.Message);
                        }
                    }
                    OnDataFilesRead(new JSEvents.DataFilesRead(file_data));
                }
            }

            bool game_exists = ffxiv_.FindProcess();

            if (game_exists != notify_state_.game_exists)
            {
                notify_state_.game_exists = game_exists;
                OnGameExists(new JSEvents.GameExistsEvent(game_exists));
            }

            bool game_active = game_active = ffxiv_.IsActive();

            if (game_active != notify_state_.game_active)
            {
                notify_state_.game_active = game_active;
                OnGameActiveChanged(new JSEvents.GameActiveChangedEvent(game_active));
            }

            // Silently stop sending other messages if the ffxiv process isn't around.
            if (!game_exists)
            {
                return(kUberSlowTimerMilli);
            }

            // onInCombatChangedEvent: Fires when entering or leaving combat.
            bool in_act_combat  = FFXIV_ACT_Plugin.ACTWrapper.InCombat;
            bool in_game_combat = ffxiv_.GetInGameCombat();

            if (!notify_state_.in_act_combat.HasValue || in_act_combat != notify_state_.in_act_combat ||
                !notify_state_.in_game_combat.HasValue || in_game_combat != notify_state_.in_game_combat)
            {
                notify_state_.in_act_combat  = in_act_combat;
                notify_state_.in_game_combat = in_game_combat;
                OnInCombatChanged(new JSEvents.InCombatChangedEvent(in_act_combat, in_game_combat));
            }

            // onZoneChangedEvent: Fires when the player changes their current zone.
            string zone_name = FFXIV_ACT_Plugin.ACTWrapper.CurrentZone;

            if (notify_state_.zone_name == null || !zone_name.Equals(notify_state_.zone_name))
            {
                notify_state_.zone_name = zone_name;
                OnZoneChanged(new JSEvents.ZoneChangedEvent(zone_name));
            }

            DateTime now = DateTime.Now;
            // The |player| can be null, such as during a zone change.
            Tuple <FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> player_data = ffxiv_.GetSelfData();
            var player      = player_data != null ? player_data.Item1 : null;
            var player_cast = player_data != null ? player_data.Item2 : null;
            // The |target| can be null when no target is selected.
            Tuple <FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> target_data = ffxiv_.GetTargetData();
            var target      = target_data != null ? target_data.Item1 : null;
            var target_cast = target_data != null ? target_data.Item2 : null;
            // The |focus| can be null when no focus target is selected.
            Tuple <FFXIVProcess.EntityData, FFXIVProcess.SpellCastingData> focus_data = ffxiv_.GetFocusData();
            var focus      = focus_data != null ? focus_data.Item1 : null;
            var focus_cast = focus_data != null ? focus_data.Item2 : null;

            // onPlayerDiedEvent: Fires when the player dies. All buffs/debuffs are
            // lost.
            if (player != null)
            {
                bool dead = player.hp == 0;
                if (dead != notify_state_.dead)
                {
                    notify_state_.dead = dead;
                    if (dead)
                    {
                        OnPlayerDied(new JSEvents.PlayerDiedEvent());
                    }
                }
            }

            // onPlayerChangedEvent: Fires when current player data changes.
            if (player != null)
            {
                bool send = false;
                if (player != notify_state_.player)
                {
                    notify_state_.player = player;
                    send = true;
                }

                if (player.job == FFXIVProcess.EntityJob.RDM)
                {
                    var job = ffxiv_.GetRedMage();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.rdm))
                        {
                            notify_state_.rdm = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.RedMageDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.WAR)
                {
                    var job = ffxiv_.GetWarrior();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.war))
                        {
                            notify_state_.war = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.WarriorDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.DRK)
                {
                    var job = ffxiv_.GetDarkKnight();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.drk))
                        {
                            notify_state_.drk = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.DarkKnightDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.PLD)
                {
                    var job = ffxiv_.GetPaladin();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.pld))
                        {
                            notify_state_.pld = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.PaladinDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.BRD)
                {
                    var job = ffxiv_.GetBard();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.brd))
                        {
                            notify_state_.brd = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.BardDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.NIN)
                {
                    var job = ffxiv_.GetNinja();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.nin))
                        {
                            notify_state_.nin = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.NinjaDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.DRG)
                {
                    var job = ffxiv_.GetDragoon();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.drg))
                        {
                            notify_state_.drg = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.DragoonDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.BLM || player.job == FFXIVProcess.EntityJob.THM)
                {
                    var job = ffxiv_.GetBlackMage();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.blm))
                        {
                            notify_state_.blm = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.BlackMageDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.WHM)
                {
                    var job = ffxiv_.GetWhiteMage();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.whm))
                        {
                            notify_state_.whm = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.WhiteMageDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.SMN || player.job == FFXIVProcess.EntityJob.SCH || player.job == FFXIVProcess.EntityJob.ACN)
                {
                    var job = ffxiv_.GetSummonerAndScholar();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.smn_sch))
                        {
                            notify_state_.smn_sch = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.SummonerAndScholarDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.MNK || player.job == FFXIVProcess.EntityJob.PGL)
                {
                    var job = ffxiv_.GetMonk();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.mnk))
                        {
                            notify_state_.mnk = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.MonkDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.MCH)
                {
                    var job = ffxiv_.GetMachinist();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.mch))
                        {
                            notify_state_.mch = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.MachinistDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (player.job == FFXIVProcess.EntityJob.AST)
                {
                    var job = ffxiv_.GetAstrologian();
                    if (job != null)
                    {
                        if (send || !job.Equals(notify_state_.ast))
                        {
                            notify_state_.ast = job;
                            var e = new JSEvents.PlayerChangedEvent(player);
                            e.jobDetail = new JSEvents.PlayerChangedEvent.AstrologianDetail(job);
                            OnPlayerChanged(e);
                        }
                    }
                }
                else if (send)
                {
                    // TODO: SAM everything
                    // No job-specific data.
                    OnPlayerChanged(new JSEvents.PlayerChangedEvent(player));
                }
            }

            // onTargetChangedEvent: Fires when current target or their state changes.
            if (target != notify_state_.target)
            {
                notify_state_.target = target;
                if (target != null)
                {
                    OnTargetChanged(new JSEvents.TargetChangedEvent(target));
                }
                else
                {
                    OnTargetChanged(new JSEvents.TargetChangedEvent(null));
                }
            }

            // onTargetCastingEvent: Fires each tick while the target is casting, and once
            // with null when not casting.
            int target_cast_id = target_cast != null ? target_cast.casting_id : 0;

            if (target_cast_id != 0 || target_cast_id != notify_state_.target_cast_id)
            {
                notify_state_.target_cast_id = target_cast_id;
                // The game considers things to be casting once progress reaches the end for a while, as the server is
                // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
                // be casting anymore once it reaches the end.
                if (target_cast_id != 0 && target_cast.casting_time_progress < target_cast.casting_time_length)
                {
                    DateTime start = now.AddSeconds(-target_cast.casting_time_progress);
                    // If the start is within the timer interval, assume it's the same cast. Since we sample the game
                    // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
                    // helps avoid it.
                    TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
                    if (start + range < notify_state_.target_cast_start || start - range > notify_state_.target_cast_start)
                    {
                        notify_state_.target_cast_start = start;
                    }
                    TimeSpan progress = now - notify_state_.target_cast_start;
                    OnTargetCasting(new JSEvents.TargetCastingEvent(target_cast.casting_id, progress.TotalSeconds, target_cast.casting_time_length));
                }
                else
                {
                    notify_state_.target_cast_start = new DateTime();
                    OnTargetCasting(new JSEvents.TargetCastingEvent(0, 0, 0));
                }
            }

            // onFocusChangedEvent: Fires when current focus target or their state changes.
            if (focus != notify_state_.focus)
            {
                notify_state_.focus = focus;
                if (target != null)
                {
                    OnFocusChanged(new JSEvents.FocusChangedEvent(focus));
                }
                else
                {
                    OnFocusChanged(new JSEvents.FocusChangedEvent(null));
                }
            }

            // onFocusCastingEvent: Fires each tick while the focus target is casting, and
            // once with null when not casting.
            int focus_cast_id = focus_cast != null ? focus_cast.casting_id : 0;

            if (focus_cast_id != 0 || focus_cast_id != notify_state_.focus_cast_id)
            {
                notify_state_.focus_cast_id = focus_cast_id;
                // The game considers things to be casting once progress reaches the end for a while, as the server is
                // resolving lag or something. That breaks our start time tracking, so we just don't consider them to
                // be casting anymore once it reaches the end.
                if (focus_cast_id != 0 && focus_cast.casting_time_progress < focus_cast.casting_time_length)
                {
                    DateTime start = now.AddSeconds(-focus_cast.casting_time_progress);
                    // If the start is within the timer interval, assume it's the same cast. Since we sample the game
                    // at a different rate than it ticks, there will be some jitter in the progress that we see, and this
                    // helps avoid it.
                    TimeSpan range = new TimeSpan(0, 0, 0, 0, kFastTimerMilli);
                    if (start + range < notify_state_.focus_cast_start || start - range > notify_state_.focus_cast_start)
                    {
                        notify_state_.focus_cast_start = start;
                    }
                    TimeSpan progress = now - notify_state_.focus_cast_start;
                    OnFocusCasting(new JSEvents.FocusCastingEvent(focus_cast.casting_id, progress.TotalSeconds, focus_cast.casting_time_length));
                }
                else
                {
                    notify_state_.focus_cast_start = new DateTime();
                    OnFocusCasting(new JSEvents.FocusCastingEvent(0, 0, 0));
                }
            }

            // onLogEvent: Fires when new combat log events from FFXIV are available. This fires after any
            // more specific events, some of which may involve parsing the logs as well.
            List <string> logs;

            log_lines_semaphore_.Wait();
            logs       = log_lines_;
            log_lines_ = last_log_lines_;
            log_lines_semaphore_.Release();
            if (logs.Count > 0)
            {
                OnLogsChanged(new JSEvents.LogEvent(logs));
                logs.Clear();
            }
            last_log_lines_ = logs;

            fight_tracker_.Tick(DateTime.Now);

            return(game_active ? kFastTimerMilli : kSlowTimerMilli);
        }
        void svn_wc_notify_func_t_impl(IntPtr baton, IntPtr path,
			NotifyAction action, NodeKind kind, IntPtr mime_type,
			NotifyState content_state, NotifyState prop_state, long revision)
        {
            string actiondesc = action.ToString();
                switch (action) {
                    case NotifyAction.UpdateAdd: actiondesc = "Added"; break;
                    case NotifyAction.UpdateDelete: actiondesc = "Deleted"; break;
                    case NotifyAction.UpdateUpdate: actiondesc = "Updating"; break;
                    case NotifyAction.UpdateExternal: actiondesc = "External Updated"; break;
                    case NotifyAction.UpdateCompleted: actiondesc = "Finished"; break;

                    case NotifyAction.CommitAdded: actiondesc = "Added"; break;
                    case NotifyAction.CommitDeleted: actiondesc = "Deleted"; break;
                    case NotifyAction.CommitModified: actiondesc = "Modified"; break;
                    case NotifyAction.CommitReplaced: actiondesc = "Replaced"; break;
                    case NotifyAction.CommitPostfixTxDelta: actiondesc = "Sending Content"; break;
            /*Add,
            Copy,
            Delete,
            Restore,
            Revert,
            FailedRevert,
            Resolved,
            Skip,
            StatusCompleted,
            StatusExternal,
            BlameRevision*/
                }

                if (updatecallback != null)
                    updatecallback(Marshal.PtrToStringAnsi(path), actiondesc);
        }