1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 02:53:21 +08:00

Compare commits

...

16 Commits

Author SHA1 Message Date
Huo Yaoyuan
1548f17aa7
Merge 7ece8ec1dc into f09d8f097a 2024-12-03 13:59:24 +01:00
Dan Balasescu
f09d8f097a
Merge pull request #30953 from peppy/notification-while-chedcking-for-updates
Show an ongoing operation when checking for updates
2024-12-03 17:27:10 +09:00
Dean Herbert
457957d3b8
Refactor check-update flow to better handle unobserved exceptions 2024-12-03 14:23:10 +09:00
Dean Herbert
2ceb3f6f85
Show an ongoing operation when checking for updates
Addresses https://github.com/ppy/osu/discussions/30950.
2024-12-03 13:43:20 +09:00
Huo Yaoyuan
7ece8ec1dc Update for suggestions 2024-12-03 00:03:59 +08:00
Huo Yaoyuan
68f4fa5a57 Turn off CA1507 2024-12-03 00:00:31 +08:00
Huo Yaoyuan
68f21709a8 Fix CA1865 2024-11-30 02:32:09 +08:00
Huo Yaoyuan
fa3c95c296 Merge branch 'master' 2024-11-30 01:07:08 +08:00
Huo Yaoyuan
fced254594 Enable selected rules for usage 2024-11-28 22:33:03 +08:00
Huo Yaoyuan
0a8ec4db2b Enable recommended rules for reliability 2024-11-28 22:20:36 +08:00
Huo Yaoyuan
5b63d725c5 Mark CA1826/CA1860 as suggestion 2024-11-28 22:13:59 +08:00
Huo Yaoyuan
f5a7716509 Apply minor performance rules 2024-11-28 20:41:44 +08:00
Huo Yaoyuan
13d7c6a2d8 Enable recommended rules for maintainability 2024-11-28 20:41:41 +08:00
Huo Yaoyuan
cd9b5927eb Enable recommended rules for globalization 2024-11-28 20:41:35 +08:00
Huo Yaoyuan
c57ace0b5f Enable recommended rules for documentation 2024-11-28 20:41:32 +08:00
Huo Yaoyuan
8f6e5c4754 Convert legacy ruleset to globalconfig 2024-11-28 19:34:56 +08:00
29 changed files with 167 additions and 139 deletions

View File

@ -46,8 +46,59 @@ dotnet_diagnostic.IDE0130.severity = warning
# IDE1006: Naming style # IDE1006: Naming style
dotnet_diagnostic.IDE1006.severity = warning dotnet_diagnostic.IDE1006.severity = warning
#Disable operator overloads requiring alternate named methods # CA1305: Specify IFormatProvider
dotnet_diagnostic.CA2225.severity = none # Too many noisy warnings for parsing/formatting numbers
dotnet_diagnostic.CA1305.severity = none
# CA1507: Use nameof to express symbol names
# Flaggs serialization name attributes
dotnet_diagnostic.CA1507.severity = suggestion
# CA1806: Do not ignore method results
# The usages for numeric parsing are explicitly optional
dotnet_diagnostic.CA1806.severity = suggestion
# CA1822: Mark members as static
# Potential false positive around reflection/too much noise
dotnet_diagnostic.CA1822.severity = none
# CA1826: Do not use Enumerable method on indexable collections
dotnet_diagnostic.CA1826.severity = suggestion
# CA1859: Use concrete types when possible for improved performance
# Involves design considerations
dotnet_diagnostic.CA1859.severity = suggestion
# CA1860: Avoid using 'Enumerable.Any()' extension method
dotnet_diagnostic.CA1860.severity = suggestion
# CA1861: Avoid constant arrays as arguments
# Outdated with collection expressions
dotnet_diagnostic.CA1861.severity = suggestion
# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = warning
# CA2016: Forward the 'CancellationToken' parameter to methods
# Some overloads are having special handling for debugger
dotnet_diagnostic.CA2016.severity = suggestion
# CA2021: Do not call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
# Causing a lot of false positives with generics
dotnet_diagnostic.CA2021.severity = none
# CA2101: Specify marshaling for P/Invoke string arguments
# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport
dotnet_diagnostic.CA2101.severity = none
# CA2201: Do not raise reserved exception types
dotnet_diagnostic.CA2201.severity = warning
# CA2208: Instantiate argument exceptions correctly
dotnet_diagnostic.CA2208.severity = suggestion
# CA2242: Test for NaN correctly
dotnet_diagnostic.CA2242.severity = warning
# Banned APIs # Banned APIs
dotnet_diagnostic.RS0030.severity = error dotnet_diagnostic.RS0030.severity = error

View File

@ -14,10 +14,6 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead. M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead. M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead.
M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead. M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead.

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
<Rule Id="CA1016" Action="None" />
<Rule Id="CA1028" Action="None" />
<Rule Id="CA1031" Action="None" />
<Rule Id="CA1034" Action="None" />
<Rule Id="CA1036" Action="None" />
<Rule Id="CA1040" Action="None" />
<Rule Id="CA1044" Action="None" />
<Rule Id="CA1051" Action="None" />
<Rule Id="CA1054" Action="None" />
<Rule Id="CA1056" Action="None" />
<Rule Id="CA1062" Action="None" />
<Rule Id="CA1063" Action="None" />
<Rule Id="CA1067" Action="None" />
<Rule Id="CA1707" Action="None" />
<Rule Id="CA1710" Action="None" />
<Rule Id="CA1714" Action="None" />
<Rule Id="CA1716" Action="None" />
<Rule Id="CA1717" Action="None" />
<Rule Id="CA1720" Action="None" />
<Rule Id="CA1721" Action="None" />
<Rule Id="CA1724" Action="None" />
<Rule Id="CA1801" Action="None" />
<Rule Id="CA1806" Action="None" />
<Rule Id="CA1812" Action="None" />
<Rule Id="CA1814" Action="None" />
<Rule Id="CA1815" Action="None" />
<Rule Id="CA1819" Action="None" />
<Rule Id="CA1822" Action="None" />
<Rule Id="CA1823" Action="None" />
<Rule Id="CA2007" Action="Warning" />
<Rule Id="CA2214" Action="None" />
<Rule Id="CA2227" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeQuality.CSharp.Analyzers" RuleNamespace="Microsoft.CodeQuality.CSharp.Analyzers">
<Rule Id="CA1001" Action="None" />
<Rule Id="CA1032" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
<Rule Id="CA1303" Action="None" />
<Rule Id="CA1304" Action="None" />
<Rule Id="CA1305" Action="None" />
<Rule Id="CA1307" Action="None" />
<Rule Id="CA1308" Action="None" />
<Rule Id="CA1816" Action="None" />
<Rule Id="CA1826" Action="None" />
<Rule Id="CA2000" Action="None" />
<Rule Id="CA2008" Action="None" />
<Rule Id="CA2213" Action="None" />
<Rule Id="CA2235" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.NetCore.CSharp.Analyzers" RuleNamespace="Microsoft.NetCore.CSharp.Analyzers">
<Rule Id="CA1309" Action="Warning" />
<Rule Id="CA2201" Action="Warning" />
</Rules>
</RuleSet>

View File

@ -20,7 +20,17 @@
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" /> <AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Code Analysis"> <PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet> <AnalysisMode>Default</AnalysisMode>
<AnalysisModeDesign>Default</AnalysisModeDesign>
<AnalysisModeDocumentation>Recommended</AnalysisModeDocumentation>
<AnalysisModeGlobalization>Recommended</AnalysisModeGlobalization>
<AnalysisModeInteroperability>Recommended</AnalysisModeInteroperability>
<AnalysisModeMaintainability>Recommended</AnalysisModeMaintainability>
<AnalysisModeNaming>Default</AnalysisModeNaming>
<AnalysisModePerformance>Minimum</AnalysisModePerformance>
<AnalysisModeReliability>Recommended</AnalysisModeReliability>
<AnalysisModeSecurity>Default</AnalysisModeSecurity>
<AnalysisModeUsage>Default</AnalysisModeUsage>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Documentation"> <PropertyGroup Label="Documentation">
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 1: return colour_cyan; case 1: return colour_cyan;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 3: case 3:
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 2: return colour_cyan; case 2: return colour_cyan;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 4: case 4:
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 3: return colour_purple; case 3: return colour_purple;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 5: case 5:
@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 4: return colour_cyan; case 4: return colour_cyan;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 6: case 6:
@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 5: return colour_pink; case 5: return colour_pink;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 7: case 7:
@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 6: return colour_pink; case 6: return colour_pink;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 8: case 8:
@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 7: return colour_purple; case 7: return colour_purple;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 9: case 9:
@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 8: return colour_purple; case 8: return colour_purple;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
case 10: case 10:
@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 9: return colour_purple; case 9: return colour_purple;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
} }
@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 5: return colour_green; case 5: return colour_green;
default: throw new ArgumentOutOfRangeException(); default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
} }
} }
} }

View File

@ -0,0 +1,7 @@
# Higher global_level has higher priority, the default global_level
# is 100 for root .globalconfig and 0 for others
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/configuration-files#precedence
is_global = true
global_level = 101
dotnet_diagnostic.CA2007.severity = none

View File

@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay
string? filePath = null; string? filePath = null;
// Files starting with _ are temporary, created by CreateFileSafely call. // Files starting with _ are temporary, created by CreateFileSafely call.
AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null); AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith('_')), () => Is.Not.Null);
AddUntilStep("filesize is non-zero", () => AddUntilStep("filesize is non-zero", () =>
{ {
try try

View File

@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("make second user host", () => MultiplayerClient.TransferHost(3)); AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0); AddUntilStep("kick buttons not visible", () => !this.ChildrenOfType<ParticipantPanel.KickButton>().Any(d => d.IsPresent));
AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id)); AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id));

View File

@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("click delete option", () => AddStep("click delete option", () =>
{ {
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete")); InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase)));
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });

View File

@ -12,9 +12,9 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Code Analysis"> <ItemGroup Label="Code Analysis">
<CodeAnalysisRuleSet>tests.ruleset</CodeAnalysisRuleSet> <GlobalAnalyzerConfigFiles Include="CodeAnalysis.tests.globalconfig" />
</PropertyGroup> </ItemGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
<Rule Id="CA2007" Action="None" />
</Rules>
</RuleSet>

View File

@ -248,29 +248,30 @@ namespace osu.Game.Database
return new RealmLive<T>(realmObject, realm); return new RealmLive<T>(realmObject, realm);
} }
#pragma warning disable RS0030 // mentioning banned symbols in documentation
/// <summary> /// <summary>
/// Register a callback to be invoked each time this <see cref="T:Realms.IRealmCollection`1" /> changes. /// Register a callback to be invoked each time this <see cref="IRealmCollection{T}" /> changes.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This adds osu! specific thread and managed state safety checks on top of <see cref="IRealmCollection{T}.SubscribeForNotifications"/>. /// This adds osu! specific thread and managed state safety checks on top of <see cref="IRealmCollection{T}.SubscribeForNotifications"/>.
/// </para> /// </para>
/// <para> /// <para>
/// The first callback will be invoked with the initial <see cref="T:Realms.IRealmCollection`1" /> after the asynchronous query completes, /// The first callback will be invoked with the initial <see cref="IRealmCollection{T}" /> after the asynchronous query completes,
/// and then called again after each write transaction which changes either any of the objects in the collection, or /// and then called again after each write transaction which changes either any of the objects in the collection, or
/// which objects are in the collection. The <c>changes</c> parameter will /// which objects are in the collection. The <c>changes</c> parameter will
/// be <c>null</c> the first time the callback is invoked with the initial results. For each call after that, /// be <c>null</c> the first time the callback is invoked with the initial results. For each call after that,
/// it will contain information about which rows in the results were added, removed or modified. /// it will contain information about which rows in the results were added, removed or modified.
/// </para> /// </para>
/// <para> /// <para>
/// If a write transaction did not modify any objects in this <see cref="T:Realms.IRealmCollection`1" />, the callback is not invoked at all. /// If a write transaction did not modify any objects in this <see cref="IRealmCollection{T}" />, the callback is not invoked at all.
/// If an error occurs the callback will be invoked with <c>null</c> for the <c>sender</c> parameter and a non-<c>null</c> <c>error</c>. /// If an error occurs the callback will be invoked with <c>null</c> for the <c>sender</c> parameter and a non-<c>null</c> <c>error</c>.
/// Currently the only errors that can occur are when opening the <see cref="T:Realms.Realm" /> on the background worker thread. /// Currently the only errors that can occur are when opening the <see cref="Realm" /> on the background worker thread.
/// </para> /// </para>
/// <para> /// <para>
/// At the time when the block is called, the <see cref="T:Realms.IRealmCollection`1" /> object will be fully evaluated /// At the time when the block is called, the <see cref="IRealmCollection{T}" /> object will be fully evaluated
/// and up-to-date, and as long as you do not perform a write transaction on the same thread /// and up-to-date, and as long as you do not perform a write transaction on the same thread
/// or explicitly call <see cref="M:Realms.Realm.Refresh" />, accessing it will never perform blocking work. /// or explicitly call <see cref="Realm.Refresh" />, accessing it will never perform blocking work.
/// </para> /// </para>
/// <para> /// <para>
/// Notifications are delivered via the standard event loop, and so can't be delivered while the event loop is blocked by other activity. /// Notifications are delivered via the standard event loop, and so can't be delivered while the event loop is blocked by other activity.
@ -279,13 +280,14 @@ namespace osu.Game.Database
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="collection">The <see cref="IRealmCollection{T}"/> to observe for changes.</param> /// <param name="collection">The <see cref="IRealmCollection{T}"/> to observe for changes.</param>
/// <param name="callback">The callback to be invoked with the updated <see cref="T:Realms.IRealmCollection`1" />.</param> /// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}" />.</param>
/// <returns> /// <returns>
/// A subscription token. It must be kept alive for as long as you want to receive change notifications. /// A subscription token. It must be kept alive for as long as you want to receive change notifications.
/// To stop receiving notifications, call <see cref="M:System.IDisposable.Dispose" />. /// To stop receiving notifications, call <see cref="IDisposable.Dispose" />.
/// </returns> /// </returns>
/// <seealso cref="M:Realms.CollectionExtensions.SubscribeForNotifications``1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0})" /> /// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IList{T}, NotificationCallbackDelegate{T})" />
/// <seealso cref="M:Realms.CollectionExtensions.SubscribeForNotifications``1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0})" /> /// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IQueryable{T}, NotificationCallbackDelegate{T})" />
#pragma warning restore RS0030
public static IDisposable QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> callback) public static IDisposable QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> callback)
where T : RealmObjectBase where T : RealmObjectBase
{ {

View File

@ -60,7 +60,7 @@ namespace osu.Game.Extensions
public static string ToCamelCase(this string input) public static string ToCamelCase(this string input)
{ {
string word = input.ToPascalCase(); string word = input.ToPascalCase();
return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word; return word.Length > 0 ? char.ToLowerInvariant(word[0]) + word.Substring(1) : word;
} }
/// <summary> /// <summary>

View File

@ -44,6 +44,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates"); public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
/// <summary>
/// "Checking for updates"
/// </summary>
public static LocalisableString CheckingForUpdates => new TranslatableString(getKey(@"checking_for_updates"), @"Checking for updates");
/// <summary> /// <summary>
/// "Open osu! folder" /// "Open osu! folder"
/// </summary> /// </summary>

View File

@ -363,7 +363,7 @@ namespace osu.Game.Online.Leaderboards
return null; return null;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException(nameof(state));
} }
} }

View File

@ -87,7 +87,7 @@ namespace osu.Game.Online
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException(nameof(state.NewValue));
} }
}); });

View File

@ -126,7 +126,7 @@ namespace osu.Game.Overlays
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException(nameof(state.NewValue));
} }
} }
} }

View File

@ -386,10 +386,8 @@ namespace osu.Game.Overlays
{ {
channelList.RemoveChannel(channel); channelList.RemoveChannel(channel);
if (loadedChannels.ContainsKey(channel)) if (loadedChannels.Remove(channel, out var loaded))
{ {
DrawableChannel loaded = loadedChannels[channel];
loadedChannels.Remove(channel);
// DrawableChannel removed from cache must be manually disposed // DrawableChannel removed from cache must be manually disposed
loaded.Dispose(); loaded.Dispose();
} }

View File

@ -244,7 +244,7 @@ namespace osu.Game.Overlays.Comments
protected void OnSuccess(CommentBundle response) protected void OnSuccess(CommentBundle response)
{ {
commentCounter.Current.Value = response.Total; commentCounter.Current.Value = response.Total;
newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && m.Type == type.Value.ToString().ToSnakeCase().ToLowerInvariant()); newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && string.Equals(m.Type, type.Value.ToString().ToSnakeCase(), StringComparison.OrdinalIgnoreCase));
if (!response.Comments.Any()) if (!response.Comments.Any())
{ {

View File

@ -4,7 +4,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
@ -13,6 +12,7 @@ using osu.Framework.Screens;
using osu.Framework.Statistics; using osu.Framework.Statistics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater; using osu.Game.Updater;
@ -36,8 +36,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved] [Resolved]
private Storage storage { get; set; } = null!; private Storage storage { get; set; } = null!;
[Resolved]
private OsuGame? game { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, OsuGame? game) private void load(OsuConfigManager config)
{ {
Add(new SettingsEnumDropdown<ReleaseStream> Add(new SettingsEnumDropdown<ReleaseStream>
{ {
@ -50,23 +53,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Add(checkForUpdatesButton = new SettingsButton Add(checkForUpdatesButton = new SettingsButton
{ {
Text = GeneralSettingsStrings.CheckUpdate, Text = GeneralSettingsStrings.CheckUpdate,
Action = () => Action = () => checkForUpdates().FireAndForget()
{
checkForUpdatesButton.Enabled.Value = false;
Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() =>
{
if (!task.GetResultSafely())
{
notifications?.Post(new SimpleNotification
{
Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version),
Icon = FontAwesome.Solid.CheckCircle,
});
}
checkForUpdatesButton.Enabled.Value = true;
}));
}
}); });
} }
@ -94,6 +81,44 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
} }
private async Task checkForUpdates()
{
if (updateManager == null || game == null)
return;
checkForUpdatesButton.Enabled.Value = false;
var checkingNotification = new ProgressNotification
{
Text = GeneralSettingsStrings.CheckingForUpdates,
};
notifications?.Post(checkingNotification);
try
{
bool foundUpdate = await updateManager.CheckForUpdateAsync().ConfigureAwait(true);
if (!foundUpdate)
{
notifications?.Post(new SimpleNotification
{
Text = GeneralSettingsStrings.RunningLatestRelease(game.Version),
Icon = FontAwesome.Solid.CheckCircle,
});
}
}
catch
{
}
finally
{
// This sequence allows the notification to be immediately dismissed.
checkingNotification.State = ProgressNotificationState.Cancelled;
checkingNotification.Close(false);
checkForUpdatesButton.Enabled.Value = true;
}
}
private void exportLogs() private void exportLogs()
{ {
ProgressNotification notification = new ProgressNotification ProgressNotification notification = new ProgressNotification

View File

@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Toolbar
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException(nameof(state.NewValue));
} }
}); });
} }

View File

@ -173,10 +173,10 @@ namespace osu.Game.Rulesets.Mods
}; };
drawable.OnRevertResult += (_, result) => drawable.OnRevertResult += (_, result) =>
{ {
if (!ratesForRewinding.ContainsKey(result.HitObject)) return; if (!ratesForRewinding.TryGetValue(result.HitObject, out double rate)) return;
if (!shouldProcessResult(result)) return; if (!shouldProcessResult(result)) return;
recentRates.Insert(0, ratesForRewinding[result.HitObject]); recentRates.Insert(0, rate);
ratesForRewinding.Remove(result.HitObject); ratesForRewinding.Remove(result.HitObject);
recentRates.RemoveAt(recentRates.Count - 1); recentRates.RemoveAt(recentRates.Count - 1);

View File

@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
return PathType.CATMULL; return PathType.CATMULL;
case 'B': case 'B':
if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) if (input.Length > 1 && int.TryParse(input.AsSpan(1), out int degree) && degree > 0)
return PathType.BSpline(degree); return PathType.BSpline(degree);
return PathType.BEZIER; return PathType.BEZIER;

View File

@ -59,14 +59,14 @@ namespace osu.Game.Screens.Ranking.Statistics.User
new SimpleStatisticTable.Spacer(), new SimpleStatisticTable.Spacer(),
new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
}, },
new Drawable[] { }, [],
new Drawable[] new Drawable[]
{ {
new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
new SimpleStatisticTable.Spacer(), new SimpleStatisticTable.Spacer(),
new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
}, },
new Drawable[] { }, [],
new Drawable[] new Drawable[]
{ {
new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },

View File

@ -37,7 +37,7 @@ namespace osu.Game.Skinning
protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; protected override string[] HashableFileTypes => new[] { ".ini", ".json" };
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == @".osk"; protected override bool ShouldDeleteArchive(string path) => string.Equals(Path.GetExtension(path), @".osk", StringComparison.OrdinalIgnoreCase);
protected override SkinInfo CreateModel(ArchiveReader archive, ImportParameters parameters) => new SkinInfo { Name = archive.Name ?? @"No name" }; protected override SkinInfo CreateModel(ArchiveReader archive, ImportParameters parameters) => new SkinInfo { Name = archive.Name ?? @"No name" };

View File

@ -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.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -91,7 +92,7 @@ namespace osu.Game.Storyboards
// Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints. // Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints.
backgroundPath = backgroundPath.ToLowerInvariant(); backgroundPath = backgroundPath.ToLowerInvariant();
return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); return GetLayer("Background").Elements.Any(e => string.Equals(e.Path, backgroundPath, StringComparison.OrdinalIgnoreCase));
} }
} }

View File

@ -78,12 +78,12 @@ namespace osu.Game.Tests.Visual.Spectator
/// <param name="state">The spectator state to end play with.</param> /// <param name="state">The spectator state to end play with.</param>
public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit)
{ {
if (!userBeatmapDictionary.TryGetValue(userId, out int value)) if (!userBeatmapDictionary.TryGetValue(userId, out int beatmapId))
return; return;
((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState
{ {
BeatmapID = value, BeatmapID = beatmapId,
RulesetID = 0, RulesetID = 0,
Mods = userModsDictionary[userId], Mods = userModsDictionary[userId],
State = state State = state

View File

@ -666,10 +666,7 @@ namespace osu.Game.Utils
{ {
// 2020-10-07 jbialogrodzki #730 Since this is public API we should probably // 2020-10-07 jbialogrodzki #730 Since this is public API we should probably
// handle null arguments? It doesn't seem to have been done consistently in this class though. // handle null arguments? It doesn't seem to have been done consistently in this class though.
if (coefficients == null) ArgumentNullException.ThrowIfNull(coefficients);
{
throw new ArgumentNullException(nameof(coefficients));
}
// 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling.
// Without this check, we attempted to peek coefficients at negative indices! // Without this check, we attempted to peek coefficients at negative indices!

View File

@ -60,7 +60,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
osu.Android.props = osu.Android.props osu.Android.props = osu.Android.props
osu.iOS.props = osu.iOS.props osu.iOS.props = osu.iOS.props
CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset global.json = global.json
osu.sln.DotSettings = osu.sln.DotSettings osu.sln.DotSettings = osu.sln.DotSettings
osu.TestProject.props = osu.TestProject.props osu.TestProject.props = osu.TestProject.props
EndProjectSection EndProjectSection