Keeps tournament information synchronized via polling
상속: IDisposable, INotifyPropertyChanged
예제 #1
0
        public ObservableParticipant(Participant participant, TournamentContext context)
        {
            source         = participant;
            MiscProperties = new Dirtyable <ParticipantMiscProperties>(ParticipantMiscProperties.Parse(Misc));
            OwningContext  = context;

            //Check tournament start date, if it is later than missing time, clear the player's missing status. This happens in the case of a bracket reset
            var tournamentStart = context.Tournament.StartedAt;

            if (UtcTimeMissing.HasValue && tournamentStart.HasValue && UtcTimeMissing.Value.ToLocalTime() < tournamentStart.Value)
            {
                SetMissing(false);
            }

            //Do a similar check on station assignment time
            if (UtcTimeMatchAssigned.HasValue && tournamentStart.HasValue && UtcTimeMatchAssigned.Value.ToLocalTime() < tournamentStart.Value)
            {
                ClearStationAssignment();
            }

            //Listen for when properties changed to that changed events for the convenience properties can also be fired.
            this.PropertyChanged += (sender, e) =>
            {
                switch (e.PropertyName)
                {
                case "Misc":
                    //Handle misc string changes by parsing the new values and raising the properties that have changed
                    var oldMiscProperties = MiscProperties.Value;

                    //Suggest new value for misc properties. This will be ignored if dirty
                    MiscProperties.SuggestValue(ParticipantMiscProperties.Parse(Misc));
                    var miscProperties = MiscProperties.Value;

                    //Check for changes from old to new, raise those properties if changed
                    if (!object.Equals(oldMiscProperties.UtcTimeMissing, miscProperties.UtcTimeMissing))
                    {
                        this.Raise("UtcTimeMissing", PropertyChanged);
                    }
                    if (!object.Equals(oldMiscProperties.UtcTimeMatchAssigned, miscProperties.UtcTimeMatchAssigned))
                    {
                        this.Raise("UtcTimeMatchAssigned", PropertyChanged);
                    }
                    if (!object.Equals(oldMiscProperties.StationAssignment, miscProperties.StationAssignment))
                    {
                        this.Raise("StationAssignment", PropertyChanged);
                    }
                    break;

                case "UtcTimeMissing":
                    this.Raise("IsMissing", PropertyChanged);
                    this.Raise("TimeSinceMissing", PropertyChanged);
                    break;

                case "UtcTimeMatchAssigned":
                    this.Raise("TimeSinceAssigned", PropertyChanged);
                    this.Raise("IsAssignedToStation", PropertyChanged);
                    break;
                }
            };
        }
        public ObservableMatch(Match match, TournamentContext context)
        {
            source = match;
            OwningContext = context;

            //Round doesn't change, initialize RoundFixed
            var totalPlayerCount = context.Tournament.ParticipantsCount;
            var lowerBoundExponent = Math.Floor(Math.Log(totalPlayerCount, 2));

            var lowerBound = Math.Pow(2, lowerBoundExponent);
            if (match.Round < 0 && totalPlayerCount > lowerBound && totalPlayerCount <= lowerBound + (lowerBound / 2))
            {
                Round = match.Round - 1;
            }
            else Round = match.Round;

            //Check if station assignment data checks out. If not, clear the assignment
            var player1Station = Player1 != null ? Player1.StationAssignment : default(string);
            var player2Station = Player2 != null ? Player2.StationAssignment : default(string);

            //If stations don't match, clear. Don't check completed matches because those will frequently have mismatching stations
            if (State != "complete" && player1Station != player2Station) ClearStationAssignment();

            //Listen for when properties changed to that changed events for the convenience properties can also be fired.
            this.PropertyChanged += (sender, e) =>
            {
                switch (e.PropertyName)
                {
                    case "Player1Id":
                        this.Raise("Player1", PropertyChanged);
                        this.Raise("PlayerCount", PropertyChanged);
                        if (Player1 != null) Player1.IsMissing = false; //When a player gets added to a match, clear their missing flag
                        break;
                    case "Player2Id":
                        this.Raise("Player2", PropertyChanged);
                        this.Raise("PlayerCount", PropertyChanged);
                        if (Player2 != null) Player2.IsMissing = false; //When a player gets added to a match, clear their missing flag
                        break;
                    case "Player1PrereqMatchId":
                        this.Raise("Player1PreviousMatch", PropertyChanged);
                        break;
                    case "Player2PrereqMatchId":
                        this.Raise("Player2PreviousMatch", PropertyChanged);
                        break;
                    case "StartedAt":
                        this.Raise("TimeSinceAvailable", PropertyChanged);
                        break;
                    case "State":
                        //Clear station assignments if match state changes
                        if (Player1 != null) Player1.ClearStationAssignment();
                        if (Player2 != null) Player2.ClearStationAssignment();

                        //If match state has changed to open, execute selected new match option
                        if (State == "open")
                        {
                            var option = GlobalSettings.Instance.SelectedNewMatchAction;

                            switch (option)
                            {
                                case NewMatchAction.AutoAssign:
                                    //TODO: Consider using lock block here to prevent potential multithreaded assignment to the same station
                                    var highestPriorityStation = Stations.Instance.GetBestNormalStation();
                                    if (highestPriorityStation != null) AssignPlayersToStation(highestPriorityStation.Name);
                                    break;
                                case NewMatchAction.Anywhere:
                                    AssignPlayersToStation("Any");
                                    break;
                            }
                        }
                        break;
                }
            };

            var propertyChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(h => this.PropertyChanged += h, h => this.PropertyChanged -= h);

            //The following will create an observable sequence that will raise an event either when player1 changes or when player1's station assignment status changes
            var player1ChangedOrAssignmentChanged = propertyChangedObs.Where(a => a.EventArgs.PropertyName == "Player1")
                .Select(_ =>
                {
                    if (Player1 != null)
                    {
                        return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(h =>
                            {
                                player1Queue.Enqueue(Player1);
                                Player1.PropertyChanged += h;
                            }, h =>
                            {
                                player1Queue.Dequeue().PropertyChanged -= h;
                            })
                            .Where(a => a.EventArgs.PropertyName == "IsAssignedToStation" || a.EventArgs.PropertyName == "StationAssignment")
                            .Select(_2 => EventArgs.Empty).StartWith(EventArgs.Empty);
                    }
                    else return Observable.Return(EventArgs.Empty);
                }).Switch();

            //Subscribe to above observable sequence to maintain the assignment state of the match
            player1ChangedOrAssignmentChanged.Subscribe(_ =>
            {
                IsMatchInProgress = Player1 != null && Player1.IsAssignedToStation;
                StationAssignment = Player1 == null ? null : Player1.StationAssignment;
            });

            //Forcibly raise player1 property notification to assign station status
            this.Raise("Player1", PropertyChanged);
        }
 public ObservableTournament(Tournament tournament, TournamentContext context)
 {
     source = tournament;
     OwningContext = context;
 }
        public MainViewModel()
        {
            CurrentScreen = ScreenType.ApiKey;

            //Observable.Start(() =>
            //{
            //    try
            //    {
            //        //I'm considering doing an http request to smashboards to find if a new version is released. I think smashboard's anti-DDOS protection is preventing it from working
            //        WebRequest request = WebRequest.Create(ThreadUrl);
            //        request.Credentials = CredentialCache.DefaultCredentials;

            //        WebResponse response = request.GetResponse();
            //        if (((HttpWebResponse)response).StatusDescription == "OK")
            //        {
            //            Stream dataStream = response.GetResponseStream();
            //            StreamReader reader = new StreamReader(dataStream);
            //            string responseFromServer = reader.ReadToEnd();
            //            reader.Close();
            //        }
            //        response.Close();
            //    }
            //    catch { /* ignore */ }
            //});

            //Modify ViewModel state when an action is initiated
            Action startAction = () =>
            {
                ErrorMessage = null;
                IsBusy = true;
            };

            //Modify ViewModel state when an action is completed
            Action endAction = () =>
            {
                IsBusy = false;
            };

            //Modify ViewModel state when an action comes back with an exception
            Action<Exception> errorHandler = ex =>
            {
                if (ex.InnerException is ChallongeApiException)
                {
                    var cApiEx = (ChallongeApiException)ex.InnerException;

                    if (cApiEx.Errors != null) ErrorMessage = cApiEx.Errors.Aggregate((one, two) => one + "\r\n" + two);
                    else ErrorMessage = string.Format("Error with ResponseStatus \"{0}\" and StatusCode \"{1}\". {2}", cApiEx.RestResponse.ResponseStatus,
                        cApiEx.RestResponse.StatusCode, cApiEx.RestResponse.ErrorMessage);
                }
                else
                {
                    ErrorMessage = ex.NewLineDelimitedMessages();
                }

                IsBusy = false;
            };

            var dispatcher = System.Threading.SynchronizationContext.Current;

            //Handle next button press
            NextCommand = Command.CreateAsync(() => true, () =>
            {
                switch (CurrentScreen)
                {
                    case ScreenType.ApiKey:
                        var subdomain = string.IsNullOrWhiteSpace(Subdomain) ? null : Subdomain;
                        Portal = new ChallongePortal(ApiKey, subdomain);

                        //Load list of tournaments that match apikey/subdomain
                        TournamentCollection = Portal.GetTournaments().OrderByDescending(t => t.CreatedAt).ToArray();

                        try
                        {
                            //This is a silly method for checking whether a new application version exists without me having my own website.
                            //I manage the most recent version number in the description of a tournament hosted on challonge. This code fetches that number
                            var versionCheckPortal = new ChallongePortal(ApiKey, "fizzitestorg");
                            MostRecentVersion = versionCheckPortal.GetTournaments().Where(t => t.Name == "CMDVersionTest").Select(t =>
                            {
                                //Modifying the description seems to put some html formatting into the result. This filters the description for
                                //just the version number by itself
                                var versionResult = string.Concat(t.Description.Where(c => char.IsDigit(c) || c == '.'));
                                return versionResult;
                            }).First();

                            //Check both version numbers to determine if current version is older than recent version
                            var versionCompareResult = Version.Split('.').Zip(MostRecentVersion.Split('.'), (v, mrv) =>
                            {
                                return int.Parse(v).CompareTo(int.Parse(mrv));
                            }).FirstOrDefault(i => i != 0);

                            //If app version is older than most recent version, show message
                            IsVersionOutdatedVisible = versionCompareResult < 0;
                        }
                        catch (Exception)
                        {
                            //If version check fails simply ignore the problem and move on
                            System.Diagnostics.Debug.WriteLine("Version check failed.");
                        }

                        break;
                    case ScreenType.TournamentSelection:
                        if (Context != null) Context.Dispose();
                        if (matchesChangedHandler != null) Context.Tournament.PropertyChanged -= matchesChangedHandler;

                        //Create tournament context from selected tournament
                        Context = new TournamentContext(Portal, SelectedTournament.Id);
                        Context.StartSynchronization(TimeSpan.FromMilliseconds(500), 6);

                        //Create TO View Model
                        OrgViewModel = new OrganizerViewModel(this, dispatcher);

                        //Load up matches into display matches. This is done to allow ordering of assigned matches over unassigned matches without having to refresh the view
                        DisplayMatches = Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Assigned))
                            .Concat(Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Unassigned))).ToList();

                        //This handler is used to keep matches display matches in sync with tournament context matches. If the matches in the context change, re-generate the display matches
                        matchesChangedHandler = new PropertyChangedEventHandler((sender, e) =>
                        {
                            if (e.PropertyName == "Matches")
                            {
                                if (Context.Tournament.Matches == null) DisplayMatches = null;
                                else
                                {
                                    DisplayMatches = Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Assigned))
                                        .Concat(Context.Tournament.Matches.Select(kvp => new DisplayMatch(OrgViewModel, kvp.Value, DisplayMatch.DisplayType.Unassigned))).ToList();
                                }
                            }
                        });
                        Context.Tournament.PropertyChanged += matchesChangedHandler;

                        break;
                }

                CurrentScreen = (ScreenType)((int)CurrentScreen + 1);
            }, startAction, endAction, errorHandler);

            Back = Command.CreateAsync(() => true, () =>
            {
                switch (CurrentScreen)
                {
                    case ScreenType.TournamentSelection:
                        ApiKey = null;
                        break;
                    case ScreenType.PendingMatchView:
                        if (OrgViewModel != null)
                        {
                            OrgViewModel.Dispose();
                            OrgViewModel = null;
                        }
                        break;
                }
                CurrentScreen = (ScreenType)((int)CurrentScreen - 1);
            }, startAction, endAction, errorHandler);

            IgnoreVersionNotification = Command.Create(() => true, () => IsVersionOutdatedVisible = false);
        }
예제 #5
0
        public ObservableMatch(Match match, TournamentContext context)
        {
            source        = match;
            OwningContext = context;

            //Round doesn't change, initialize RoundFixed
            var totalPlayerCount   = context.Tournament.ParticipantsCount;
            var lowerBoundExponent = Math.Floor(Math.Log(totalPlayerCount, 2));

            var lowerBound = Math.Pow(2, lowerBoundExponent);

            if (match.Round < 0 && totalPlayerCount > lowerBound && totalPlayerCount <= lowerBound + (lowerBound / 2))
            {
                Round = match.Round - 1;
            }
            else
            {
                Round = match.Round;
            }

            //Check if station assignment data checks out. If not, clear the assignment
            var player1Station = Player1 != null ? Player1.StationAssignment : default(string);
            var player2Station = Player2 != null ? Player2.StationAssignment : default(string);

            //If stations don't match, clear. Don't check completed matches because those will frequently have mismatching stations
            if (State != "complete" && player1Station != player2Station)
            {
                ClearStationAssignment();
            }

            //Listen for when properties changed to that changed events for the convenience properties can also be fired.
            this.PropertyChanged += (sender, e) =>
            {
                switch (e.PropertyName)
                {
                case "Player1Id":
                    this.Raise("Player1", PropertyChanged);
                    this.Raise("PlayerCount", PropertyChanged);
                    if (Player1 != null)
                    {
                        Player1.IsMissing = false;                      //When a player gets added to a match, clear their missing flag
                    }
                    break;

                case "Player2Id":
                    this.Raise("Player2", PropertyChanged);
                    this.Raise("PlayerCount", PropertyChanged);
                    if (Player2 != null)
                    {
                        Player2.IsMissing = false;                      //When a player gets added to a match, clear their missing flag
                    }
                    break;

                case "Player1PrereqMatchId":
                    this.Raise("Player1PreviousMatch", PropertyChanged);
                    break;

                case "Player2PrereqMatchId":
                    this.Raise("Player2PreviousMatch", PropertyChanged);
                    break;

                case "StartedAt":
                    this.Raise("TimeSinceAvailable", PropertyChanged);
                    break;

                case "State":
                    //Clear station assignments if match state changes
                    if (Player1 != null)
                    {
                        Player1.ClearStationAssignment();
                    }
                    if (Player2 != null)
                    {
                        Player2.ClearStationAssignment();
                    }

                    //If match state has changed to open, execute selected new match option
                    if (State == "open")
                    {
                        var option = GlobalSettings.Instance.SelectedNewMatchAction;

                        switch (option)
                        {
                        case NewMatchAction.AutoAssign:
                            //TODO: Consider using lock block here to prevent potential multithreaded assignment to the same station
                            var highestPriorityStation = Stations.Instance.GetBestNormalStation();
                            if (highestPriorityStation != null)
                            {
                                AssignPlayersToStation(highestPriorityStation.Name);
                            }
                            break;

                        case NewMatchAction.Anywhere:
                            AssignPlayersToStation("Any");
                            break;
                        }
                    }
                    break;
                }
            };

            var propertyChangedObs = Observable.FromEventPattern <PropertyChangedEventHandler, PropertyChangedEventArgs>(h => this.PropertyChanged += h, h => this.PropertyChanged -= h);

            //The following will create an observable sequence that will raise an event either when player1 changes or when player1's station assignment status changes
            var player1ChangedOrAssignmentChanged = propertyChangedObs.Where(a => a.EventArgs.PropertyName == "Player1")
                                                    .Select(_ =>
            {
                if (Player1 != null)
                {
                    return(Observable.FromEventPattern <PropertyChangedEventHandler, PropertyChangedEventArgs>(h =>
                    {
                        player1Queue.Enqueue(Player1);
                        Player1.PropertyChanged += h;
                    }, h =>
                    {
                        player1Queue.Dequeue().PropertyChanged -= h;
                    })
                           .Where(a => a.EventArgs.PropertyName == "IsAssignedToStation" || a.EventArgs.PropertyName == "StationAssignment")
                           .Select(_2 => EventArgs.Empty).StartWith(EventArgs.Empty));
                }
                else
                {
                    return(Observable.Return(EventArgs.Empty));
                }
            }).Switch();

            //Subscribe to above observable sequence to maintain the assignment state of the match
            player1ChangedOrAssignmentChanged.Subscribe(_ =>
            {
                IsMatchInProgress = Player1 != null && Player1.IsAssignedToStation;
                StationAssignment = Player1 == null ? null : Player1.StationAssignment;
            });

            //Forcibly raise player1 property notification to assign station status
            this.Raise("Player1", PropertyChanged);
        }
예제 #6
0
 public ObservableTournament(Tournament tournament, TournamentContext context)
 {
     source        = tournament;
     OwningContext = context;
 }