Exemple #1
0
        public void ModifyTask(VotePartition vote1, string newTask, VoteType type)
        {
            var votes = GetVoteEntries(type);

            var vote2 = vote1.ModifyTask(newTask);

            if (vote1 == vote2)
            {
                return;
            }

            if (!votes.TryGetValue(vote1, out var voters1))
            {
                voters1      = new IdentitySet();
                votes[vote1] = voters1;
            }

            if (!votes.TryGetValue(vote2, out var voters2))
            {
                voters2      = new IdentitySet();
                votes[vote2] = voters2;
            }

            // Save prior state to allow an undo
            PreserveMerge(vote1, vote2, voters1, voters2, type);

            // Update the votes->identity lookup
            voters2.UnionWith(voters1);
            voters1.Clear();
        }
Exemple #2
0
        /// <summary>
        /// Initializes a new instance of the <see cref="Plan"/> class.
        /// </summary>
        /// <param name="planName">Name of the plan.</param>
        /// <param name="sourceIdentity">The identity of the originating post.</param>
        /// <param name="content">The content (<see cref="VotePartition"/>) of the plan.</param>
        /// <param name="planType">Type of the plan.</param>
        public Plan(string planName, Identity sourceIdentity, VotePartition content, PlanType planType)
        {
            if (string.IsNullOrEmpty(planName))
            {
                throw new ArgumentNullException(nameof(planName));
            }

            Identity = new Identity(planName, sourceIdentity, IdentityType.Plan);
            Content  = content ?? throw new ArgumentNullException(nameof(content));
            PlanType = planType;
        }
Exemple #3
0
        public bool Remove(VotePartition vote1, VoteType type)
        {
            var  votes   = GetVoteEntries(type);
            bool removed = false;

            if (votes.TryGetValue(vote1, out var voters1))
            {
                if (voters1.Count > 0)
                {
                    removed = true;
                    PreserveRemovedVote(vote1, voters1, type);
                    voters1.Clear();
                }
            }

            return(removed);
        }
Exemple #4
0
        public UndoItem(UndoItemType undoType, VoteType voteType,
                        VotePartition vote1 = null, VotePartition vote2 = null,
                        Identity voter1     = null, Identity voter2     = null,
                        IEnumerable <VotePartition> votes1 = null, IEnumerable <VotePartition> votes2 = null,
                        IEnumerable <Identity> voters1     = null, IEnumerable <Identity> voters2     = null)
        {
            UndoType = undoType;
            VoteType = voteType;

            Vote1   = vote1;
            Vote2   = vote2;
            Votes1  = votes1?.ToList();
            Votes2  = votes2?.ToList();
            Voter1  = voter1;
            Voter2  = voter2;
            Voters1 = voters1?.ToList();
            Voters2 = voters2?.ToList();
        }
Exemple #5
0
 public bool Matches(VotePartition other)
 {
     return(VoteLines.Equals(other.VoteLines));
 }
Exemple #6
0
        /// <summary>
        /// Take the initial value of a post, and substitute plans and
        /// proxy vote lines as appropriate.
        /// </summary>
        /// <param name="post">The original post.</param>
        /// <returns>Returns a list of VoteLines to use for the purpose of processing the vote.</returns>
        public static List <VotePartition> GetVotePartitions(Post post, IQuest quest)
        {
            if (post == null)
            {
                throw new ArgumentNullException(nameof(post));
            }
            if (quest == null)
            {
                throw new ArgumentNullException(nameof(quest));
            }

            List <VotePartition> results   = new List <VotePartition>();
            VotePartition        partition = null;
            VoteLine             parent    = null;

            // Move through the vote lines by index so we can modify the
            // index pointer directly when doing comparisons.
            var voteLineArray = post.Vote.VoteLines.ToArray();
            int index         = 0;

            while (index < voteLineArray.Length)
            {
                // Current line.
                VoteLine line = voteLineArray[index];

                // First check if the line is a proxy vote for a plan or a user
                Match m = startsWithPlan.Match(line.ComparableContent);

                // "Plan XYZ"
                if (m.Success)
                {
                    string planname = m.Groups["planname"].Value;

                    // Plan has priority, in case the plan is named after the user.
                    if (VotingRecords.Instance.HasPlanName(planname))
                    {
                        // Get the collection of variants of this plan name.
                        var  plans = VotingRecords.Instance.GetPlans(planname);
                        Plan plan  = null;

                        // Make sure we don't go over the limit of the remaining post vote lines.
                        int remainingLines = voteLineArray.Length - index - 1;

                        if (remainingLines == 0)
                        {
                            // If there are no remaining lines in the current post, we
                            // just take the provided variant 0.
                            plan = plans.First(p => p.Identity.Number == 0);
                        }
                        else
                        {
                            // OK, there are lines that may potentially be copied from the plan.
                            // Limit the plans we're looking at to those that can fit in our remaining line space.
                            var limitedPlans = plans.Where(p => p.Content.VoteLines.Count <= remainingLines).ToList();

                            if (limitedPlans.Any())
                            {
                                foreach (var p in limitedPlans)
                                {
                                    // In each of the plans that can fit in the remaining vote space,
                                    // compare them with an equal-sized segment of the vote.
                                    // If it matches, that's a copy of the plan, and we can skip past it.
                                    var segment = new ArraySegment <VoteLine>(voteLineArray, index, p.Content.VoteLines.Count);

                                    if (segment.SequenceEqual(p.Content.VoteLines))
                                    {
                                        plan = p;
                                        break;
                                    }
                                }

                                // However if no sequence match was found, we only have the naming line to
                                // copy, and we can move on to the next line of the post.
                                if (plan == null)
                                {
                                    plan = limitedPlans.First(p => p.Identity.Number == 0);
                                }
                                else
                                {
                                    // If we did find the full match, update the index so that we increment past that.
                                    index += plan.Content.VoteLines.Count - 1;
                                }
                            }
                            else
                            {
                                // None of the selected plans can fit in the remaining lines of the post.
                                // Thus we don't need to try to match, and just take the named line.
                                plan = plans.First(p => p.Identity.Number == 0);
                            }
                        }

                        // If we found a plan, add its contents to the results and go to move next.
                        // Otherwise, drop out and treat this as a normal line.
                        if (plan != null)
                        {
                            List <VotePartition> planPartitions = VotingRecords.Instance.GetPartitionsForIdentity(plan.Identity);

                            results.AddRange(planPartitions);

                            goto moveNext;
                        }
                    }
                    // Otherwise check if it's a user proxy.  Usernames are unlikely to be allowed to be greater
                    // than 20 characters, but giving a fair buffer just in case.  Don't check for very long strings
                    // as usernames.  Also check global options in case proxy votes are disabled.
                    else if (planname.Length <= 40 && !quest.DisableProxyVotes)
                    {
                        List <VotePartition> proxyPartitions = GetProxyPartitions(planname, false, post.Identity);

                        // There are no matching identities.  Treat as a normal line.
                        if (proxyPartitions == null)
                        {
                            goto normalLine;
                        }

                        // If there are no recorded partitions for this identity, it hasn't been
                        // processed yet, and this is a future reference, or a past vote that itself
                        // has future references and hasn't completed processing yet.
                        if (!proxyPartitions.Any())
                        {
                            // If we aren't forcing the processing of this post, just bail and return null.
                            // If we *are* forcing it, we have to treat this as a normal line.
                            if (post.ForceProcess)
                            {
                                goto normalLine;
                            }
                            else
                            {
                                VotingRecords.Instance.NoteFutureReference(post);
                                return(null);
                            }
                        }

                        // Add whatever proxy partitions we got to he results and move on.
                        results.AddRange(proxyPartitions);

                        goto moveNext;
                    }
                }
                // If it doesn't start with 'plan', also check if it's a (possibly pinned) username proxy.
                // Usernames are unlikely to be allowed to be greater than 20 characters, but giving
                // a fair buffer just in case.  Don't check for very long strings as usernames.
                // Also check global options in case proxy votes are disabled.
                else
                {
                    // Proxy regex might start with ^ to indicate a pinned name, and have a
                    // username length of no more than 40 characters.
                    m = proxyRegex.Match(line.ComparableContent);

                    if (m.Success && !quest.DisableProxyVotes)
                    {
                        bool   pin      = m.Groups["pin"].Success;
                        string username = m.Groups["username"].Value;

                        List <VotePartition> proxyPartitions = GetProxyPartitions(username, pin, post.Identity);

                        // There are no matching identities.  Treat as a normal line.
                        if (proxyPartitions == null)
                        {
                            goto normalLine;
                        }

                        // If there are no recorded partitions for this identity, it hasn't been
                        // processed yet, and this is a future reference, or a past vote that itself
                        // has future references and hasn't completed processing yet.
                        if (!proxyPartitions.Any())
                        {
                            // If we aren't forcing the processing of this post, just bail and return null.
                            // If we *are* forcing it, we have to treat this as a normal line.
                            if (post.ForceProcess)
                            {
                                goto normalLine;
                            }
                            else
                            {
                                VotingRecords.Instance.NoteFutureReference(post);
                                return(null);
                            }
                        }

                        // Add whatever proxy partitions we got to he results and move on.
                        results.AddRange(proxyPartitions);

                        goto moveNext;
                    }
                    // If we have a plan name that doesn't have the 'plan' prefix, redo the logic
                    // of skipping past any copied content.
                    else if (VotingRecords.Instance.HasPlanName(line.ComparableContent))
                    {
                        string planname = line.ComparableContent;

                        // Get the collection of variants of this plan name.
                        var  plans = VotingRecords.Instance.GetPlans(planname);
                        Plan plan  = null;

                        // Make sure we don't go over the limit of the remaining post vote lines.
                        int remainingLines = voteLineArray.Length - index - 1;

                        if (remainingLines == 0)
                        {
                            // If there are no remaining lines in the current post, we
                            // just take the provided variant 0.
                            plan = plans.First(p => p.Identity.Number == 0);
                        }
                        else
                        {
                            // OK, there are lines that may potentially be copied from the plan.
                            // Limit the plans we're looking at to those that can fit in our remaining line space.
                            var limitedPlans = plans.Where(p => p.Content.VoteLines.Count <= remainingLines).ToList();

                            if (limitedPlans.Any())
                            {
                                foreach (var p in limitedPlans)
                                {
                                    // In each of the plans that can fit in the remaining vote space,
                                    // compare them with an equal-sized segment of the vote.
                                    // If it matches, that's a copy of the plan, and we can skip past it.
                                    var segment = new ArraySegment <VoteLine>(voteLineArray, index, p.Content.VoteLines.Count);

                                    if (segment.SequenceEqual(p.Content.VoteLines))
                                    {
                                        plan = p;
                                        break;
                                    }
                                }

                                // However if no sequence match was found, we only have the naming line to
                                // copy, and we can move on to the next line of the post.
                                if (plan == null)
                                {
                                    plan = limitedPlans.First(p => p.Identity.Number == 0);
                                }
                                else
                                {
                                    // If we did find the full match, update the index so that we increment past that.
                                    index += plan.Content.VoteLines.Count - 1;
                                }
                            }
                            else
                            {
                                // None of the selected plans can fit in the remaining lines of the post.
                                // Thus we don't need to try to match, and just take the named line.
                                plan = plans.First(p => p.Identity.Number == 0);
                            }
                        }

                        // If we found a plan, add its contents to the results and go to move next.
                        // Otherwise, drop out and treat this as a normal line.
                        if (plan != null)
                        {
                            List <VotePartition> planPartitions = VotingRecords.Instance.GetPartitionsForIdentity(plan.Identity);

                            results.AddRange(planPartitions);

                            goto moveNext;
                        }
                    }
                }


                // Not a plan proxy, and not a user proxy. Handle normal partitioning.
normalLine:

                if (partition == null)
                {
                    partition = new VotePartition();
                }
                if (parent == null)
                {
                    parent = line;
                }

                switch (quest.PartitionMode)
                {
                case PartitionMode.None:
                    partition.AddLine(line);
                    break;

                case PartitionMode.ByLine:
                    if (partition.VoteLines.Count > 0)
                    {
                        results.Add(partition);
                    }
                    partition = new VotePartition(line, VoteType.Vote);
                    break;

                case PartitionMode.ByLineTask:
                    if (partition.VoteLines.Count > 0)
                    {
                        results.Add(partition);
                    }

                    if (line.Prefix.Length == 0)
                    {
                        parent    = line;
                        partition = new VotePartition(line, VoteType.Vote);
                    }
                    else if (parent.Task != string.Empty && line.Task == string.Empty)
                    {
                        var mLine = line.Modify(task: parent.Task);
                        partition = new VotePartition(mLine, VoteType.Vote);
                    }
                    else
                    {
                        partition = new VotePartition(line, VoteType.Vote);
                    }
                    break;

                case PartitionMode.ByBlock:
                case PartitionMode.ByBlockAll:
                    if (line.Prefix.Length == 0)
                    {
                        if (partition.VoteLines.Count > 0)
                        {
                            results.Add(partition);
                        }
                        partition = new VotePartition(line, VoteType.Vote);
                    }
                    else
                    {
                        partition.AddLine(line);
                    }
                    break;

                default:
                    break;
                }

                // Exit point for next enumerator line.
moveNext:

                index++;
            }

            if (partition != null && partition.VoteLines.Count > 0)
            {
                results.Add(partition);
            }

            return(results);
        }
Exemple #7
0
 /// <summary>
 /// Initializes a new instance of the <see cref="Plan" /> class.
 /// </summary>
 /// <param name="identity">The identity object for the plan.</param>
 /// <param name="content">The content (<see cref="VotePartition"/>) of the plan.</param>
 /// <param name="planType">Type of the plan.</param>
 public Plan(Identity identity, VotePartition content, PlanType planType)
 {
     Identity = identity ?? throw new ArgumentNullException(nameof(identity));
     Content  = content ?? throw new ArgumentNullException(nameof(content));
     PlanType = planType;
 }
Exemple #8
0
        /// <summary>
        /// Gets the plans from a vote.
        /// </summary>
        /// <param name="vote">The vote.</param>
        /// <returns>Returns a list of all plans found within a vote.</returns>
        public static List <Plan> GetPlansFromVote(Vote vote)
        {
            if (vote == null)
            {
                throw new ArgumentNullException(nameof(vote));
            }

            if (!vote.IsValid)
            {
                throw new InvalidOperationException("This is not a valid vote.");
            }

            List <Plan> plans = new List <Plan>();

            var voteGrouping = vote.GetVoteMarkerGroups();

            bool checkForBasePlans = true;

            // Base plans
            while (checkForBasePlans && voteGrouping.Any())
            {
                var voteGroup = voteGrouping.First();

                Match m = anyPlanRegex.Match(voteGroup.Key);

                if (m.Groups["base"].Success && voteGroup.First().MarkerType == MarkerType.Vote && voteGroup.Count() > 1)
                {
                    var partition = new VotePartition(voteGroup, VoteType.Plan);
                    plans.Add(new Plan(m.Groups["planname"].Value, vote.Post.Identity, partition, PlanType.Base));
                    voteGrouping = voteGrouping.Skip(1);
                }
                else
                {
                    checkForBasePlans = false;
                }
            }

            // Vote labels
            if (voteGrouping.Any())
            {
                var voteGroup = voteGrouping.First();

                Match m = anyPlanRegex.Match(voteGroup.Key);

                if (m.Success && m.Groups["base"].Success == false && voteGroup.Count() == 1 &&
                    voteGroup.First().MarkerType == MarkerType.Vote)
                {
                    var labeledGroups = voteGrouping.TakeWhile(g => g.First().MarkerType == MarkerType.Vote ||
                                                               g.First().MarkerType == MarkerType.Approval);

                    var flattenedLines = labeledGroups.SelectMany(a => a).ToList();
                    var partition      = new VotePartition(flattenedLines, VoteType.Plan);

                    PlanType planType = labeledGroups.Skip(1).Any() ? PlanType.Label : PlanType.SingleLine;

                    voteGrouping = voteGrouping.Skip(labeledGroups.Count());

                    plans.Add(new Plan(m.Groups["planname"].Value, vote.Post.Identity, partition, planType));
                }
            }

            // Any other defined plans with content
            foreach (var voteGroup in voteGrouping)
            {
                Match m = anyPlanRegex.Match(voteGroup.Key);
                if (m.Success && voteGroup.Skip(1).Any())
                {
                    var partition = new VotePartition(voteGroup, VoteType.Plan);
                    plans.Add(new Plan(m.Groups["planname"].Value, vote.Post.Identity, partition, PlanType.Content));
                }
            }

            vote.StorePlans(plans);

            // Return all collected plans
            return(plans);
        }
Exemple #9
0
        private void PreserveRemovedVote(VotePartition vote1, IEnumerable <Identity> vote1Voters, VoteType type)
        {
            var undoItem = new UndoItem(UndoItemType.RemoveVote, type, vote1: vote1, voters1: vote1Voters);

            UndoBuffer.Push(undoItem);
        }
Exemple #10
0
        /// <summary>
        /// Preserves the specified state prior to a vote merge, to allow undoing it.
        /// </summary>
        /// <param name="vote1">The vote1.</param>
        /// <param name="vote2">The vote2.</param>
        /// <param name="voters1">The voters1.</param>
        /// <param name="voters2">The voters2.</param>
        /// <param name="type">The type.</param>
        private void PreserveMerge(VotePartition vote1, VotePartition vote2, IEnumerable <Identity> voters1, IEnumerable <Identity> voters2, VoteType type)
        {
            var undoItem = new UndoItem(UndoItemType.Merge, type, vote1: vote1, vote2: vote2, voters1: voters1, voters2: voters2);

            UndoBuffer.Push(undoItem);
        }