Ctrl K
ComunidadGuías…

src/ClipMerge.cs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
using System;
using System.Collections.Generic;
using System.IO;

namespace Clipman
{
    internal enum ClipMergeKind
    {
        Text,
        Files
    }

    internal sealed class ClipMergeObservation
    {
        public ClipMergeKind Kind { get; set; }
        public string Signature { get; set; }
        public string SourceApplication { get; set; }
        public string Operation { get; set; }
        public string HistoryId { get; set; }
        public uint ChangeIdentifier { get; set; }
        public object Payload { get; set; }

        public ClipMergeObservation()
        {
            Signature = string.Empty;
            SourceApplication = string.Empty;
            Operation = string.Empty;
            HistoryId = string.Empty;
        }
    }

    internal sealed class ClipMergeDecision
    {
        public bool ShouldMerge { get; private set; }
        public bool SuppressDuplicate { get; private set; }
        public ClipMergeObservation Base { get; private set; }
        public ClipMergeObservation FirstTap { get; private set; }

        private ClipMergeDecision()
        {
        }

        public static ClipMergeDecision Replace()
        {
            return new ClipMergeDecision();
        }

        public static ClipMergeDecision Merge(ClipMergeObservation baseItem, ClipMergeObservation firstTap)
        {
            return new ClipMergeDecision
            {
                ShouldMerge = true,
                Base = baseItem,
                FirstTap = firstTap
            };
        }

        public static ClipMergeDecision Duplicate()
        {
            return new ClipMergeDecision { SuppressDuplicate = true };
        }
    }

    internal sealed class ClipMergeDetector
    {
        public const int DefaultWindowMilliseconds = 500;
        public const int MinimumWindowMilliseconds = 200;
        public const int MaximumWindowMilliseconds = 2000;
        private ClipMergeObservation current;
        private ClipMergeObservation candidateBase;
        private ClipMergeObservation candidateFirstTap;
        private long candidateStartedMilliseconds;

        public ClipMergeDecision Observe(ClipMergeObservation incoming, long nowMilliseconds, bool enabled, int windowMilliseconds, bool deliberate)
        {
            if (incoming == null) throw new ArgumentNullException("incoming");
            windowMilliseconds = NormalizeWindow(windowMilliseconds);

            if (!enabled || deliberate || string.IsNullOrWhiteSpace(incoming.SourceApplication))
            {
                candidateBase = null;
                candidateFirstTap = null;
                current = incoming;
                return ClipMergeDecision.Replace();
            }

            var elapsed = nowMilliseconds - candidateStartedMilliseconds;
            if (candidateFirstTap != null && SameTap(candidateFirstTap, incoming) &&
                IsDuplicateNotification(candidateFirstTap, incoming, elapsed))
            {
                candidateStartedMilliseconds = nowMilliseconds;
                return ClipMergeDecision.Duplicate();
            }
            if (candidateFirstTap != null && candidateBase != null && elapsed >= 0 && elapsed <= windowMilliseconds &&
                SameTap(candidateFirstTap, incoming) && Compatible(candidateBase, incoming))
            {
                var decision = ClipMergeDecision.Merge(candidateBase, candidateFirstTap);
                candidateBase = null;
                candidateFirstTap = null;
                return decision;
            }

            candidateBase = current;
            candidateFirstTap = incoming;
            candidateStartedMilliseconds = nowMilliseconds;
            current = incoming;
            return ClipMergeDecision.Replace();
        }

        public void SetCurrentHistoryId(string historyId)
        {
            if (current != null) current.HistoryId = historyId ?? string.Empty;
            if (candidateFirstTap != null && ReferenceEquals(current, candidateFirstTap))
            {
                candidateFirstTap.HistoryId = historyId ?? string.Empty;
            }
        }

        public void CompleteMerge(ClipMergeObservation merged, string historyId)
        {
            if (merged == null) throw new ArgumentNullException("merged");
            merged.HistoryId = historyId ?? string.Empty;
            current = merged;
            candidateBase = null;
            candidateFirstTap = null;
        }

        public void RetainFirstTap(ClipMergeObservation firstTap)
        {
            current = firstTap;
            candidateBase = null;
            candidateFirstTap = null;
        }

        public void Reset()
        {
            current = null;
            candidateBase = null;
            candidateFirstTap = null;
            candidateStartedMilliseconds = 0;
        }

        public static int NormalizeWindow(int value)
        {
            return Math.Max(MinimumWindowMilliseconds, Math.Min(MaximumWindowMilliseconds, value));
        }

        public static string ResolveSeparator(string mode, string custom)
        {
            switch ((mode ?? string.Empty).Trim().ToUpperInvariant())
            {
                case "BLANKLINE": return Environment.NewLine + Environment.NewLine;
                case "SPACE": return " ";
                case "COMMASPACE": return ", ";
                case "CUSTOM": return DecodeCustomSeparator(custom);
                case "NEWLINE":
                default: return Environment.NewLine;
            }
        }

        private static string DecodeCustomSeparator(string value)
        {
            return (value ?? string.Empty)
                .Replace("\\r\\n", "\r\n")
                .Replace("\\n", "\n")
                .Replace("\\r", "\r")
                .Replace("\\t", "\t");
        }

        private static bool SameTap(ClipMergeObservation left, ClipMergeObservation right)
        {
            return left.Kind == right.Kind &&
                string.Equals(left.Signature, right.Signature, StringComparison.Ordinal) &&
                string.Equals(left.SourceApplication, right.SourceApplication, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(left.Operation, right.Operation, StringComparison.OrdinalIgnoreCase);
        }

        private static bool IsDuplicateNotification(ClipMergeObservation left, ClipMergeObservation right, long elapsedMilliseconds)
        {
            var compatibility = ClipboardApplicationCompatibility.ForProcess(left.SourceApplication);
            if (left.ChangeIdentifier != 0 && right.ChangeIdentifier != 0)
            {
                if (left.ChangeIdentifier == right.ChangeIdentifier) return true;
                return compatibility.AcceptChangingSequenceIdentifiers &&
                    elapsedMilliseconds >= 0 &&
                    elapsedMilliseconds < compatibility.DuplicateNotificationMilliseconds;
            }
            return elapsedMilliseconds >= 0 && elapsedMilliseconds < compatibility.DuplicateNotificationMilliseconds;
        }

        private static bool Compatible(ClipMergeObservation baseItem, ClipMergeObservation incoming)
        {
            return baseItem.Kind == incoming.Kind &&
                !string.Equals(baseItem.Signature, incoming.Signature, StringComparison.Ordinal) &&
                (incoming.Kind != ClipMergeKind.Files ||
                 string.Equals(baseItem.Operation, incoming.Operation, StringComparison.OrdinalIgnoreCase));
        }
    }

    internal static class ClipMergeFilePolicy
    {
        public static bool AreSourcesAvailable(IEnumerable<string> paths)
        {
            if (paths == null) return false;
            var found = false;
            foreach (var value in paths)
            {
                var path = (value ?? string.Empty).Trim();
                if (path.Length == 0) return false;
                found = true;
                if (!File.Exists(path) && !Directory.Exists(path)) return false;
            }
            return found;
        }
    }
}