134 lines
6.0 KiB
C#
134 lines
6.0 KiB
C#
|
using System;
|
|||
|
using System.Linq;
|
|||
|
using System.Globalization;
|
|||
|
using System.Collections.Generic;
|
|||
|
|
|||
|
using BFR.Helpers;
|
|||
|
using BFR.DataModels;
|
|||
|
|
|||
|
using Avalonia;
|
|||
|
|
|||
|
namespace BFR.Operations
|
|||
|
{
|
|||
|
public class Sort : Operation
|
|||
|
{
|
|||
|
public override string Name => nameof(Sort);
|
|||
|
public override string Description => "Sorts the file names according to how they are when they arrive at this operation.";
|
|||
|
|
|||
|
public static readonly AvaloniaProperty<SortMode> ModeProperty =
|
|||
|
AvaloniaProperty.Register<MainWindow, SortMode>(nameof(Mode), SortMode.Normal);
|
|||
|
public SortMode Mode { get => GetValue(ModeProperty); set => SetValue(ModeProperty, value); }
|
|||
|
public SortDirection Direction { get; set; }
|
|||
|
|
|||
|
public SortOperationMode[] Modes => SortOperationMode.Modes;
|
|||
|
public string[] Directions => Enum.GetNames(typeof(SortDirection));
|
|||
|
|
|||
|
public bool FullName { get; set; } = false;
|
|||
|
|
|||
|
private Func<FileModel, string> GetName => FullName
|
|||
|
? (Func<FileModel, string>)(x => x.FullName)
|
|||
|
: (Func<FileModel, string>)(x => x.Name);
|
|||
|
|
|||
|
protected override void ApplyToInternal(IList<FileModel> files)
|
|||
|
{
|
|||
|
// Fail conditions: No fail conditions.
|
|||
|
// Apply operation
|
|||
|
files.ReplaceAll(new List<FileModel> (Mode switch
|
|||
|
{
|
|||
|
SortMode.Normal => files.OrderBy(GetName, StringComparer.CurrentCulture),
|
|||
|
SortMode.Natural => files.OrderBy(GetName, Comparer<string>.Create(CompareNatural)),
|
|||
|
SortMode.Ordinal => files.OrderBy(GetName, StringComparer.Ordinal),
|
|||
|
SortMode.Length => files.OrderBy(x => GetName(x).Length),
|
|||
|
SortMode.Reverse => files.Reverse(),
|
|||
|
_ => files
|
|||
|
}));
|
|||
|
if (Mode != SortMode.Reverse && Direction == SortDirection.Descending)
|
|||
|
files.ReplaceAll(new List<FileModel>(files.Reverse()));
|
|||
|
}
|
|||
|
|
|||
|
// Code taken and slightly modified from https://git.lastassault.de/speatzle/BulkFileRenamer
|
|||
|
public static int CompareNatural(string strA, string strB) =>
|
|||
|
CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
|
|||
|
|
|||
|
public static int CompareNatural(string strA, string strB, CultureInfo culture, CompareOptions options)
|
|||
|
{
|
|||
|
var cmp = culture.CompareInfo;
|
|||
|
var (iA, iB) = (0, 0);
|
|||
|
var softResult = 0;
|
|||
|
var softResultWeight = 0;
|
|||
|
while (iA < strA.Length && iB < strB.Length)
|
|||
|
{
|
|||
|
var isDigitA = char.IsDigit(strA[iA]);
|
|||
|
var isDigitB = char.IsDigit(strB[iB]);
|
|||
|
if (isDigitA != isDigitB)
|
|||
|
return cmp.Compare(strA, iA, strB, iB, options);
|
|||
|
else if (!isDigitA && !isDigitB)
|
|||
|
{
|
|||
|
var jA = iA + 1;
|
|||
|
var jB = iB + 1;
|
|||
|
while (jA < strA.Length && !char.IsDigit(strA[jA])) jA++;
|
|||
|
while (jB < strB.Length && !char.IsDigit(strB[jB])) jB++;
|
|||
|
var cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options);
|
|||
|
if (cmpResult != 0)
|
|||
|
{
|
|||
|
var (secA, secB) = (strA[iA..jA], strB[iB..jB]);
|
|||
|
if (cmp.Compare(secA + "1", secB + "2", options) ==
|
|||
|
cmp.Compare(secA + "2", secB + "1", options))
|
|||
|
return cmp.Compare(strA, iA, strB, iB, options);
|
|||
|
else if (softResultWeight < 1)
|
|||
|
{
|
|||
|
softResult = cmpResult;
|
|||
|
softResultWeight = 1;
|
|||
|
}
|
|||
|
}
|
|||
|
(iA, jA) = (iB, jB);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
var zeroA = (char)(strA[iA] - (int)char.GetNumericValue(strA[iA]));
|
|||
|
var zeroB = (char)(strB[iB] - (int)char.GetNumericValue(strB[iB]));
|
|||
|
var (jA, jB) = (iA, iB);
|
|||
|
while (jA < strA.Length && strA[jA] == zeroA) jA++;
|
|||
|
while (jB < strB.Length && strB[jB] == zeroB) jB++;
|
|||
|
int resultIfSameLength = 0;
|
|||
|
do
|
|||
|
{
|
|||
|
isDigitA = jA < strA.Length && char.IsDigit(strA[jA]);
|
|||
|
isDigitB = jB < strB.Length && char.IsDigit(strB[jB]);
|
|||
|
var numA = isDigitA ? (int)char.GetNumericValue(strA[jA]) : 0;
|
|||
|
var numB = isDigitB ? (int)char.GetNumericValue(strB[jB]) : 0;
|
|||
|
if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false;
|
|||
|
if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false;
|
|||
|
if (isDigitA && isDigitB)
|
|||
|
{
|
|||
|
if (numA != numB && resultIfSameLength == 0)
|
|||
|
resultIfSameLength = numA < numB ? -1 : 1;
|
|||
|
jA++;
|
|||
|
jB++;
|
|||
|
}
|
|||
|
}
|
|||
|
while (isDigitA && isDigitB);
|
|||
|
if (isDigitA != isDigitB)
|
|||
|
return isDigitA ? 1 : -1;
|
|||
|
else if (resultIfSameLength != 0)
|
|||
|
return resultIfSameLength;
|
|||
|
var (lA, lB) = (jA - iA, jB - iB);
|
|||
|
if (lA != lB)
|
|||
|
return lA > lB ? -1 : 1;
|
|||
|
else if (zeroA != zeroB && softResultWeight < 2)
|
|||
|
{
|
|||
|
softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options);
|
|||
|
softResultWeight = 2;
|
|||
|
}
|
|||
|
(iA, iB) = (jA, jB);
|
|||
|
}
|
|||
|
}
|
|||
|
if (iA < strA.Length || iB < strB.Length)
|
|||
|
return iA < strA.Length ? 1 : -1;
|
|||
|
else if (softResult != 0)
|
|||
|
return softResult;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|