mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge pull request #23129 from Elvendir/add-last-played-filter
Add last played search filter in song select
This commit is contained in:
commit
692ff8ea11
@ -454,5 +454,111 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly object[] correct_date_query_examples =
|
||||||
|
{
|
||||||
|
new object[] { "600" },
|
||||||
|
new object[] { "0.5s" },
|
||||||
|
new object[] { "120m" },
|
||||||
|
new object[] { "48h120s" },
|
||||||
|
new object[] { "10y24M" },
|
||||||
|
new object[] { "10y60d120s" },
|
||||||
|
new object[] { "0y0M2d" },
|
||||||
|
new object[] { "1y1M2d" }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(correct_date_query_examples))]
|
||||||
|
public void TestValidDateQueries(string dateQuery)
|
||||||
|
{
|
||||||
|
string query = $"played<{dateQuery} time";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object[] incorrect_date_query_examples =
|
||||||
|
{
|
||||||
|
new object[] { ".5s" },
|
||||||
|
new object[] { "7m27" },
|
||||||
|
new object[] { "7m7m7m" },
|
||||||
|
new object[] { "5s6m" },
|
||||||
|
new object[] { "7d7y" },
|
||||||
|
new object[] { "0:3:6" },
|
||||||
|
new object[] { "0:3:" },
|
||||||
|
new object[] { "\"three days\"" },
|
||||||
|
new object[] { "0.1y0.1M2d" },
|
||||||
|
new object[] { "0.99y0.99M2d" },
|
||||||
|
new object[] { string.Empty }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(incorrect_date_query_examples))]
|
||||||
|
public void TestInvalidDateQueries(string dateQuery)
|
||||||
|
{
|
||||||
|
string query = $"played<{dateQuery} time";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGreaterDateQuery()
|
||||||
|
{
|
||||||
|
const string query = "played>50";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Min, Is.Null);
|
||||||
|
// the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
|
||||||
|
// (irrelevant in proportion to the actual filter proscribed).
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLowerDateQuery()
|
||||||
|
{
|
||||||
|
const string query = "played<50";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
||||||
|
// the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
|
||||||
|
// (irrelevant in proportion to the actual filter proscribed).
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBothSidesDateQuery()
|
||||||
|
{
|
||||||
|
const string query = "played>3M played<1y6M";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
||||||
|
// the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
|
||||||
|
// (irrelevant in proportion to the actual filter proscribed).
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddYears(-1).AddMonths(-6)).Within(TimeSpan.FromSeconds(5)));
|
||||||
|
Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddMonths(-3)).Within(TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEqualDateQuery()
|
||||||
|
{
|
||||||
|
const string query = "played=50";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOutOfRangeDateQuery()
|
||||||
|
{
|
||||||
|
const string query = "played<10000y";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||||
|
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
@ -64,6 +65,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize);
|
match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize);
|
||||||
match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty);
|
match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty);
|
||||||
match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length);
|
match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length);
|
||||||
|
match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue);
|
||||||
match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM);
|
match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM);
|
||||||
|
|
||||||
match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor);
|
match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor);
|
||||||
|
@ -35,6 +35,7 @@ namespace osu.Game.Screens.Select
|
|||||||
public OptionalRange<double> BPM;
|
public OptionalRange<double> BPM;
|
||||||
public OptionalRange<int> BeatDivisor;
|
public OptionalRange<int> BeatDivisor;
|
||||||
public OptionalRange<BeatmapOnlineStatus> OnlineStatus;
|
public OptionalRange<BeatmapOnlineStatus> OnlineStatus;
|
||||||
|
public OptionalRange<DateTimeOffset> LastPlayed;
|
||||||
public OptionalTextFilter Creator;
|
public OptionalTextFilter Creator;
|
||||||
public OptionalTextFilter Artist;
|
public OptionalTextFilter Artist;
|
||||||
public OptionalTextFilter Title;
|
public OptionalTextFilter Title;
|
||||||
|
@ -61,6 +61,10 @@ namespace osu.Game.Screens.Select
|
|||||||
case "length":
|
case "length":
|
||||||
return tryUpdateLengthRange(criteria, op, value);
|
return tryUpdateLengthRange(criteria, op, value);
|
||||||
|
|
||||||
|
case "played":
|
||||||
|
case "lastplayed":
|
||||||
|
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
|
||||||
|
|
||||||
case "divisor":
|
case "divisor":
|
||||||
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
||||||
|
|
||||||
@ -376,5 +380,107 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0);
|
return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This function is intended for parsing "days / months / years ago" type filters.
|
||||||
|
/// </summary>
|
||||||
|
private static bool tryUpdateDateAgoRange(ref FilterCriteria.OptionalRange<DateTimeOffset> dateRange, Operator op, string val)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case Operator.Equal:
|
||||||
|
// an equality filter is difficult to define for support here.
|
||||||
|
// if "3 months 2 days ago" means a single concrete time instant, such a filter is basically useless.
|
||||||
|
// if it means a range of 24 hours, then that is annoying to write and also comes with its own implications
|
||||||
|
// (does it mean "time instant 3 months 2 days ago, within 12 hours of tolerance either direction"?
|
||||||
|
// does it mean "the full calendar day, from midnight to midnight, 3 months 2 days ago"?)
|
||||||
|
// as such, for simplicity, just refuse to support this.
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// for the remaining operators, since the value provided to this function is an "ago" type value
|
||||||
|
// (as in, referring to some amount of time back),
|
||||||
|
// we'll want to flip the operator, such that `>5d` means "more than five days ago", as in "*before* five days ago",
|
||||||
|
// as intended by the user.
|
||||||
|
case Operator.Less:
|
||||||
|
op = Operator.Greater;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operator.LessOrEqual:
|
||||||
|
op = Operator.GreaterOrEqual;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operator.Greater:
|
||||||
|
op = Operator.Less;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Operator.GreaterOrEqual:
|
||||||
|
op = Operator.LessOrEqual;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupCollection? match = null;
|
||||||
|
|
||||||
|
match ??= tryMatchRegex(val, @"^((?<years>\d+)y)?((?<months>\d+)M)?((?<days>\d+(\.\d+)?)d)?((?<hours>\d+(\.\d+)?)h)?((?<minutes>\d+(\.\d+)?)m)?((?<seconds>\d+(\.\d+)?)s)?$");
|
||||||
|
match ??= tryMatchRegex(val, @"^(?<days>\d+(\.\d+)?)$");
|
||||||
|
|
||||||
|
if (match == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DateTimeOffset? dateTimeOffset = null;
|
||||||
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<string> keys = new List<string> { @"seconds", @"minutes", @"hours", @"days", @"months", @"years" };
|
||||||
|
|
||||||
|
foreach (string key in keys)
|
||||||
|
{
|
||||||
|
if (!match.TryGetValue(key, out var group) || !group.Success)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (group.Success)
|
||||||
|
{
|
||||||
|
if (!tryParseDoubleWithPoint(group.Value, out double length))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case @"seconds":
|
||||||
|
dateTimeOffset = (dateTimeOffset ?? now).AddSeconds(-length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case @"minutes":
|
||||||
|
dateTimeOffset = (dateTimeOffset ?? now).AddMinutes(-length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case @"hours":
|
||||||
|
dateTimeOffset = (dateTimeOffset ?? now).AddHours(-length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case @"days":
|
||||||
|
dateTimeOffset = (dateTimeOffset ?? now).AddDays(-length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case @"months":
|
||||||
|
dateTimeOffset = (dateTimeOffset ?? now).AddMonths(-(int)length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case @"years":
|
||||||
|
dateTimeOffset = (dateTimeOffset ?? now).AddYears(-(int)length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ArgumentOutOfRangeException)
|
||||||
|
{
|
||||||
|
dateTimeOffset = DateTimeOffset.MinValue.AddMilliseconds(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dateTimeOffset.HasValue)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user