From aef54ef41be69e3576f7e412a689926eccf0e632 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:02:23 -0500 Subject: [PATCH 01/11] Fix CA2013: stop passing nullable value types to ReferenceEquals ReferenceEquals on a long?/DateTime? boxes the value, so the null checks only worked by the accident that a null nullable boxes to a null reference. Use plain null comparisons instead. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- WaveBox.Server/src/Api/HttpContextProcessor.cs | 10 +++++----- .../src/ApiHandler/Handlers/ArtApiHandler.cs | 2 +- .../src/Subsonic/Handlers/SubsonicMediaHandlers.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WaveBox.Server/src/Api/HttpContextProcessor.cs b/WaveBox.Server/src/Api/HttpContextProcessor.cs index dd008e2..ba9a8ec 100644 --- a/WaveBox.Server/src/Api/HttpContextProcessor.cs +++ b/WaveBox.Server/src/Api/HttpContextProcessor.cs @@ -163,11 +163,11 @@ public void WriteFile(Stream fs, int startOffset, long length, string mimeType, } long contentLength = length - actualStartOffset; - if (!ReferenceEquals(limitToBytes, null) && contentLength > limitToBytes) { + if (limitToBytes != null && contentLength > limitToBytes) { contentLength = (long)limitToBytes; } - bool isPartial = startOffset != 0 || !ReferenceEquals(limitToBytes, null); + bool isPartial = startOffset != 0 || limitToBytes != null; if (isPartial) { if (ReferenceEquals(customHeaders, null)) { customHeaders = new Dictionary(); @@ -188,7 +188,7 @@ public void WriteFile(Stream fs, int startOffset, long length, string mimeType, } int thisChunkSize = chunkSize; - if (!ReferenceEquals(limitToBytes, null)) { + if (limitToBytes != null) { // Make sure we don't send too much data on the last (potentially) partial chunk if (bytesWritten + chunkSize > limitToBytes) { thisChunkSize = (int)(limitToBytes - bytesWritten); @@ -205,7 +205,7 @@ public void WriteFile(Stream fs, int startOffset, long length, string mimeType, totalBytesWritten += bytesRead; // See if we need to stop the transfer to limit the size - if (!ReferenceEquals(limitToBytes, null) && bytesWritten == limitToBytes) { + if (limitToBytes != null && bytesWritten == limitToBytes) { break; } @@ -236,7 +236,7 @@ public void WriteFile(Stream fs, int startOffset, long length, string mimeType, private DateTime CleanLastModified(DateTime? lastModified) { // If null, use current time - if (ReferenceEquals(lastModified, null)) { + if (lastModified == null) { return DateTime.UtcNow; } diff --git a/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs index 7b14b59..92ab857 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs @@ -79,7 +79,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } DateTime? lastModified = null; - if (!ReferenceEquals(art.LastModified, null)) { + if (art.LastModified != null) { lastModified = ((long)art.LastModified).ToDateTime(); } processor.WriteFile(stream, 0, stream.Length, HttpHeader.MimeTypeForExtension(".jpg"), null, true, lastModified); diff --git a/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs b/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs index 3a92cc5..def3dc4 100644 --- a/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs +++ b/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs @@ -44,7 +44,7 @@ public static void GetCoverArt(SubsonicRequest req, HttpContextProcessor process } DateTime? lastModified = null; - if (!ReferenceEquals(art.LastModified, null)) { + if (art.LastModified != null) { lastModified = ((long)art.LastModified).ToDateTime(); } processor.WriteFile(stream, 0, stream.Length, "image/jpeg", null, true, lastModified); From 92ba4a73efdfb207cd939347b0e51337d4fb9246 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:03:49 -0500 Subject: [PATCH 02/11] Fix unchecked TryParse results and remove dead transcode variables Check TryParse success explicitly at each call site instead of relying on the out value being zeroed on failure. This also fixes a real bug in ArtApiHandler: a failed parse of the size parameter overwrote the Int32.MaxValue sentinel with 0, so an invalid size attempted a 0-pixel resize instead of skipping the resize. The stream/length locals in TranscodeApiHandler were leftovers from before the TranscodeStreamer refactor and are now deleted. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- .../src/ApiHandler/Handlers/ArtApiHandler.cs | 28 +++++++------------ .../ApiHandler/Handlers/StreamApiHandler.cs | 4 +-- .../Handlers/TranscodeApiHandler.cs | 14 ++++------ 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs index 92ab857..7a41fd9 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/ArtApiHandler.cs @@ -38,9 +38,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for blur (value between 0 and 100) double blurSigma = 0; - if (uri.Parameters.ContainsKey("blur")) { - int blur = 0; - Int32.TryParse(uri.Parameters["blur"], out blur); + if (uri.Parameters.TryGetValue("blur", out string blurParam) && Int32.TryParse(blurParam, out int blur)) { if (blur < 0) { blur = 0; } else if (blur > 100) { @@ -60,21 +58,15 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { return; } - // If art size requested... - if (uri.Parameters.ContainsKey("size")) { - int size = Int32.MaxValue; - Int32.TryParse(uri.Parameters["size"], out size); - - // Parse size if valid - if (size != Int32.MaxValue) { - try { - Stream resized = ArtStream.ResizeImage(stream, size, blurSigma); - stream.Close(); - stream = resized; - } catch (Exception e) { - logger.Error("Error resizing art, returning original: ", e); - stream.Position = 0; - } + // If a valid art size requested, resize + if (uri.Parameters.TryGetValue("size", out string sizeParam) && Int32.TryParse(sizeParam, out int size)) { + try { + Stream resized = ArtStream.ResizeImage(stream, size, blurSigma); + stream.Close(); + stream = resized; + } catch (Exception e) { + logger.Error("Error resizing art, returning original: ", e); + stream.Position = 0; } } diff --git a/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs index 8c1b10b..3742461 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs @@ -41,8 +41,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Try to get seconds float seconds = 0f; - if (uri.Parameters.ContainsKey("seconds")) { - float.TryParse(uri.Parameters["seconds"], out seconds); + if (uri.Parameters.TryGetValue("seconds", out string secondsParam) && float.TryParse(secondsParam, out float parsedSeconds)) { + seconds = parsedSeconds; } try { diff --git a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs index dccd427..6061fdd 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs @@ -46,8 +46,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Get seconds offset float seconds = 0f; - if (uri.Parameters.ContainsKey("seconds")) { - float.TryParse(uri.Parameters["seconds"], out seconds); + if (uri.Parameters.TryGetValue("seconds", out string secondsParam) && float.TryParse(secondsParam, out float parsedSeconds)) { + seconds = parsedSeconds; } // Verify ID received @@ -62,10 +62,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { IMediaItem item = null; TranscodeType transType = TranscodeType.MP3; bool isDirect = false; - Stream stream = null; int startOffset = 0; long? limitToSize = null; - long length = 0; bool estimateContentLength = false; // Optionally estimate content length @@ -191,13 +189,13 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for offset seconds and length seconds parameters uint offsetSeconds = 0; - if (uri.Parameters.ContainsKey("offsetSeconds")) { - UInt32.TryParse(uri.Parameters["offsetSeconds"], out offsetSeconds); + if (uri.Parameters.TryGetValue("offsetSeconds", out string offsetSecondsParam) && UInt32.TryParse(offsetSecondsParam, out uint parsedOffsetSeconds)) { + offsetSeconds = parsedOffsetSeconds; } uint lengthSeconds = 0; - if (uri.Parameters.ContainsKey("lengthSeconds")) { - UInt32.TryParse(uri.Parameters["lengthSeconds"], out lengthSeconds); + if (uri.Parameters.TryGetValue("lengthSeconds", out string lengthSecondsParam) && UInt32.TryParse(lengthSecondsParam, out uint parsedLengthSeconds)) { + lengthSeconds = parsedLengthSeconds; } // Either stream the rest of the file, or the duration specified From 2e4c2ca08c855efeb2fee2603b0bb3512a1ec748 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:04:52 -0500 Subject: [PATCH 03/11] Port Last.fm auth requests to HttpClient and encapsulate static fields WebRequest.Create is obsolete (SYSLIB0014); the two last.fm token/session GETs now go through a shared static HttpClient. Error semantics are unchanged: both APIs throw on a failed request. ServerInfo.TempFolder (never reassigned) becomes a get-only property and UserPurge.Queue becomes a property, resolving CA2211. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- WaveBox.Server/src/Lastfm.cs | 16 ++++------------ WaveBox.Server/src/ServerInfo.cs | 2 +- .../src/Service/Services/Cron/UserPurge.cs | 2 +- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/WaveBox.Server/src/Lastfm.cs b/WaveBox.Server/src/Lastfm.cs index 5e138fa..6ac0ec2 100644 --- a/WaveBox.Server/src/Lastfm.cs +++ b/WaveBox.Server/src/Lastfm.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net.Sockets; using System.Net; +using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Web; @@ -20,6 +21,7 @@ public class Lastfm { private static string apiKey = "6aec36725ab20cff28e8525cdf5fbd4a"; private static string secret = "cd596009d199d51405a2477d4e65c5d7"; + private static readonly HttpClient httpClient = new HttpClient(); private string sessionKey = null; private User user; @@ -201,12 +203,7 @@ private void GetSessionKeyAndUpdateUser(string token) { JsonNode jsonResponse; string requestUrl = String.Format("http://ws.audioscrobbler.com/2.0/?method=auth.getSession&format=json&api_key={0}&token={1}&api_sig={2}", apiKey, token, apiSig); - HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestUrl); - - using (HttpWebResponse response = req.GetResponse() as HttpWebResponse) { - StreamReader reader = new StreamReader(response.GetResponseStream()); - jsonResponse = JsonNode.Parse(reader.ReadToEnd()); - } + jsonResponse = JsonNode.Parse(httpClient.GetStringAsync(requestUrl).GetAwaiter().GetResult()); if (jsonResponse != null && jsonResponse["session"] != null) { sessionKey = jsonResponse["session"]["key"].ToString(); @@ -226,12 +223,7 @@ private void CreateAuthUrl() { // Get a last.fm request token string requestUrl = String.Format("http://ws.audioscrobbler.com/2.0/?method=auth.gettoken&format=json&api_key={0}", apiKey); - HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestUrl); - - using (HttpWebResponse response = req.GetResponse() as HttpWebResponse) { - StreamReader reader = new StreamReader(response.GetResponseStream()); - jsonResponse = JsonNode.Parse(reader.ReadToEnd()); - } + jsonResponse = JsonNode.Parse(httpClient.GetStringAsync(requestUrl).GetAwaiter().GetResult()); requestToken = jsonResponse != null && jsonResponse["token"] != null ? jsonResponse["token"].ToString() : null; diff --git a/WaveBox.Server/src/ServerInfo.cs b/WaveBox.Server/src/ServerInfo.cs index 3dc39f9..1592e4d 100644 --- a/WaveBox.Server/src/ServerInfo.cs +++ b/WaveBox.Server/src/ServerInfo.cs @@ -6,7 +6,7 @@ namespace WaveBox { public static class ServerInfo { // WaveBox temporary folder, for transcodes and such; WAVEBOX_TEMP overrides the shared // default so test runs (and concurrent instances) don't delete each other's files - public static string TempFolder = Environment.GetEnvironmentVariable("WAVEBOX_TEMP") is string overrideTemp && overrideTemp.Length > 0 + public static string TempFolder { get; } = Environment.GetEnvironmentVariable("WAVEBOX_TEMP") is string overrideTemp && overrideTemp.Length > 0 ? overrideTemp : Path.Combine(Path.GetTempPath(), "wavebox"); diff --git a/WaveBox.Server/src/Service/Services/Cron/UserPurge.cs b/WaveBox.Server/src/Service/Services/Cron/UserPurge.cs index 5daf92e..7519d4a 100644 --- a/WaveBox.Server/src/Service/Services/Cron/UserPurge.cs +++ b/WaveBox.Server/src/Service/Services/Cron/UserPurge.cs @@ -18,7 +18,7 @@ public static class UserPurge { private static readonly WaveBox.Core.Logging.ILog logger = WaveBox.Core.Logging.LogManager.GetLogger(); // Create operation queue for the session scrubber - public static DelayedOperationQueue Queue = new DelayedOperationQueue(); + public static DelayedOperationQueue Queue { get; set; } = new DelayedOperationQueue(); /// /// Start user purge operation From b010ac4baa595b81547bac50b499c2c9ba437a62 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:07:59 -0500 Subject: [PATCH 04/11] Fix CA1854: use TryGetValue instead of ContainsKey plus indexer Convert dictionary reads guarded by ContainsKey to single TryGetValue lookups across the API handlers and the session/user repository caches. No behavior change; short-circuit ordering of compound conditions is preserved at every site. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- .../src/Repository/SessionRepository.cs | 8 ++++---- WaveBox.Core/src/Repository/UserRepository.cs | 4 ++-- .../Handlers/AlbumArtistsApiHandler.cs | 12 +++++------ .../ApiHandler/Handlers/AlbumsApiHandler.cs | 8 ++++---- .../ApiHandler/Handlers/ArtistsApiHandler.cs | 12 +++++------ .../ApiHandler/Handlers/FoldersApiHandler.cs | 4 ++-- .../ApiHandler/Handlers/GenresApiHandler.cs | 4 ++-- .../Handlers/NowPlayingApiHandler.cs | 8 ++++---- .../Handlers/PlaylistsApiHandler.cs | 12 +++++------ .../ApiHandler/Handlers/SearchApiHandler.cs | 18 ++++++++--------- .../ApiHandler/Handlers/SettingsApiHandler.cs | 4 ++-- .../ApiHandler/Handlers/SongsApiHandler.cs | 8 ++++---- .../ApiHandler/Handlers/StatsApiHandler.cs | 4 ++-- .../Handlers/TranscodeApiHandler.cs | 11 +++++----- .../Handlers/TranscodeHlsApiHandler.cs | 10 +++++----- .../ApiHandler/Handlers/UsersApiHandler.cs | 20 +++++++++---------- .../ApiHandler/Handlers/VideosApiHandler.cs | 8 ++++---- WaveBox.Server/src/ApiHandler/UriWrapper.cs | 4 ++-- 18 files changed, 79 insertions(+), 80 deletions(-) diff --git a/WaveBox.Core/src/Repository/SessionRepository.cs b/WaveBox.Core/src/Repository/SessionRepository.cs index b024ffb..c490add 100644 --- a/WaveBox.Core/src/Repository/SessionRepository.cs +++ b/WaveBox.Core/src/Repository/SessionRepository.cs @@ -42,8 +42,8 @@ public Session SessionForRowId(int rowId) { public Session SessionForSessionId(string sessionId) { lock (this.Sessions) { - if (this.Sessions.ContainsKey(sessionId)) { - return this.Sessions[sessionId]; + if (this.Sessions.TryGetValue(sessionId, out Session session)) { + return session; } return null; @@ -147,8 +147,8 @@ public bool DeleteSessionsForUserId(int userId) { } lock (this.Sessions) { - if (this.Sessions.ContainsKey(sessionId)) { - return this.Sessions[sessionId].UserId; + if (this.Sessions.TryGetValue(sessionId, out Session session)) { + return session.UserId; } return null; diff --git a/WaveBox.Core/src/Repository/UserRepository.cs b/WaveBox.Core/src/Repository/UserRepository.cs index a5777d6..fe75140 100644 --- a/WaveBox.Core/src/Repository/UserRepository.cs +++ b/WaveBox.Core/src/Repository/UserRepository.cs @@ -48,8 +48,8 @@ private bool ReloadUsers() { public User UserForId(int userId) { lock (this.Users) { - if (this.Users.ContainsKey(userId)) { - return this.Users[userId]; + if (this.Users.TryGetValue(userId, out User user)) { + return user; } return new User() { UserId = userId }; diff --git a/WaveBox.Server/src/ApiHandler/Handlers/AlbumArtistsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/AlbumArtistsApiHandler.cs index 6f17160..a53c3fd 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/AlbumArtistsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/AlbumArtistsApiHandler.cs @@ -47,7 +47,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { counts.Add("albums", albums.Count); // If requested, add artist's songs to response - if (uri.Parameters.ContainsKey("includeSongs") && uri.Parameters["includeSongs"].IsTrue()) { + if (uri.Parameters.TryGetValue("includeSongs", out string includeSongsParam) && includeSongsParam.IsTrue()) { songs = a.ListOfSongs(); counts.Add("songs", songs.Count); } else { @@ -55,7 +55,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // If requested, add artist's Last.fm info to response - if (uri.Parameters.ContainsKey("lastfmInfo") && uri.Parameters["lastfmInfo"].IsTrue()) { + if (uri.Parameters.TryGetValue("lastfmInfo", out string lastfmInfoParam) && lastfmInfoParam.IsTrue()) { logger.IfInfo("Querying Last.fm for artist: " + a.AlbumArtistName); try { lastfmInfo = Lastfm.GetAlbumArtistInfo(a); @@ -75,8 +75,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } } // Check for a request for range of artists - else if (uri.Parameters.ContainsKey("range")) { - string[] range = uri.Parameters["range"].Split(','); + else if (uri.Parameters.TryGetValue("range", out string rangeParam)) { + string[] range = rangeParam.Split(','); // Ensure valid range was parsed if (range.Length != 2) { @@ -97,8 +97,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for a request to limit/paginate artists, like SQL // Note: can be combined with range or all artists - if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { - string[] limit = uri.Parameters["limit"].Split(','); + if (uri.Parameters.TryGetValue("limit", out string limitParam) && uri.Id == null) { + string[] limit = limitParam.Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { diff --git a/WaveBox.Server/src/ApiHandler/Handlers/AlbumsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/AlbumsApiHandler.cs index 97668b3..1b0ce2e 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/AlbumsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/AlbumsApiHandler.cs @@ -42,8 +42,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { songs = a.ListOfSongs(); } // Check for a request for range of songs - else if (uri.Parameters.ContainsKey("range")) { - string[] range = uri.Parameters["range"].Split(','); + else if (uri.Parameters.TryGetValue("range", out string rangeParam)) { + string[] range = rangeParam.Split(','); // Ensure valid range was parsed if (range.Length != 2) { @@ -64,8 +64,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for a request to limit/paginate songs, like SQL // Note: can be combined with range or all albums - if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { - string[] limit = uri.Parameters["limit"].Split(','); + if (uri.Parameters.TryGetValue("limit", out string limitParam) && uri.Id == null) { + string[] limit = limitParam.Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { diff --git a/WaveBox.Server/src/ApiHandler/Handlers/ArtistsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/ArtistsApiHandler.cs index 3d78d28..6fe37dc 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/ArtistsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/ArtistsApiHandler.cs @@ -52,7 +52,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { counts.Add("albums", albums.Count); // If requested, add artist's songs to response - if (uri.Parameters.ContainsKey("includeSongs") && uri.Parameters["includeSongs"].IsTrue()) { + if (uri.Parameters.TryGetValue("includeSongs", out string includeSongsParam) && includeSongsParam.IsTrue()) { songs = a.ListOfSongs(); counts.Add("songs", songs.Count); } else { @@ -60,7 +60,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // If requested, add artist's Last.fm info to response - if (uri.Parameters.ContainsKey("lastfmInfo") && uri.Parameters["lastfmInfo"].IsTrue()) { + if (uri.Parameters.TryGetValue("lastfmInfo", out string lastfmInfoParam) && lastfmInfoParam.IsTrue()) { logger.IfInfo("Querying Last.fm for artist: " + a.ArtistName); try { lastfmInfo = Lastfm.GetArtistInfo(a); @@ -76,8 +76,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { counts.Add("favorites", numFavorites); } // Check for a request for range of artists - else if (uri.Parameters.ContainsKey("range")) { - string[] range = uri.Parameters["range"].Split(','); + else if (uri.Parameters.TryGetValue("range", out string rangeParam)) { + string[] range = rangeParam.Split(','); // Ensure valid range was parsed if (range.Length != 2) { @@ -98,8 +98,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for a request to limit/paginate artists, like SQL // Note: can be combined with range or all artists - if (uri.Parameters.ContainsKey("limit") && !uri.Parameters.ContainsKey("id")) { - string[] limit = uri.Parameters["limit"].Split(','); + if (uri.Parameters.TryGetValue("limit", out string limitParam) && !uri.Parameters.ContainsKey("id")) { + string[] limit = limitParam.Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { diff --git a/WaveBox.Server/src/ApiHandler/Handlers/FoldersApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/FoldersApiHandler.cs index c284faa..6035842 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/FoldersApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/FoldersApiHandler.cs @@ -40,7 +40,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { containingFolder = Injection.Get().FolderForId((int)uri.Id); listOfFolders = containingFolder.ListOfSubFolders(); - if (uri.Parameters.ContainsKey("recursiveMedia") && uri.Parameters["recursiveMedia"].IsTrue()) { + if (uri.Parameters.TryGetValue("recursiveMedia", out string recursiveMediaParam) && recursiveMediaParam.IsTrue()) { recursive = true; } @@ -54,7 +54,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // No id parameter - if (uri.Parameters.ContainsKey("mediaFolders") && uri.Parameters["mediaFolders"].IsTrue()) { + if (uri.Parameters.TryGetValue("mediaFolders", out string mediaFoldersParam) && mediaFoldersParam.IsTrue()) { // They asked for the media folders listOfFolders = Injection.Get().MediaFolders(); } else { diff --git a/WaveBox.Server/src/ApiHandler/Handlers/GenresApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/GenresApiHandler.cs index 72a353c..5b5b9fe 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/GenresApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/GenresApiHandler.cs @@ -36,8 +36,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { if (uri.Id != null) { // Default: artists string type = "artists"; - if (uri.Parameters.ContainsKey("type")) { - type = uri.Parameters["type"]; + if (uri.Parameters.TryGetValue("type", out string typeParam)) { + type = typeParam; } // Get single genre, add it for output diff --git a/WaveBox.Server/src/ApiHandler/Handlers/NowPlayingApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/NowPlayingApiHandler.cs index 1eb4d29..0e17616 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/NowPlayingApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/NowPlayingApiHandler.cs @@ -37,13 +37,13 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { IList nowPlaying = nowPlayingService.Playing; // Filter by user name - if (uri.Parameters.ContainsKey("user")) { - nowPlaying = nowPlaying.Where(x => x.User.UserName == uri.Parameters["user"]).ToList(); + if (uri.Parameters.TryGetValue("user", out string userName)) { + nowPlaying = nowPlaying.Where(x => x.User.UserName == userName).ToList(); } // Filter by client name - if (uri.Parameters.ContainsKey("client")) { - nowPlaying = nowPlaying.Where(x => x.User.CurrentSession.ClientName == uri.Parameters["client"]).ToList(); + if (uri.Parameters.TryGetValue("client", out string clientName)) { + nowPlaying = nowPlaying.Where(x => x.User.CurrentSession.ClientName == clientName).ToList(); } // Return list of now playing items diff --git a/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs index 52bf1e4..be86320 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs @@ -51,8 +51,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { if (uri.Action == "create") { // Try to get the name string name = null; - if (uri.Parameters.ContainsKey("name")) { - name = HttpUtility.UrlDecode(uri.Parameters["name"]); + if (uri.Parameters.TryGetValue("name", out string nameParam)) { + name = HttpUtility.UrlDecode(nameParam); } // Verify non-null name @@ -243,8 +243,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { private IList ParseItemIds(UriWrapper uri) { // Try to get the itemIds IList itemIds = new List(); - if (uri.Parameters.ContainsKey("itemIds")) { - string[] itemIdStrings = uri.Parameters["itemIds"].Split(','); + if (uri.Parameters.TryGetValue("itemIds", out string itemIdsParam)) { + string[] itemIdStrings = itemIdsParam.Split(','); foreach (string itemIdString in itemIdStrings) { int itemId; @@ -260,8 +260,8 @@ private IList ParseItemIds(UriWrapper uri) { private IList ParseIndexes(UriWrapper uri) { // Try to get the itemIds IList itemIds = new List(); - if (uri.Parameters.ContainsKey("indexes")) { - string[] itemIdStrings = uri.Parameters["indexes"].Split(','); + if (uri.Parameters.TryGetValue("indexes", out string indexesParam)) { + string[] itemIdStrings = indexesParam.Split(','); foreach (string itemIdString in itemIdStrings) { int itemId; diff --git a/WaveBox.Server/src/ApiHandler/Handlers/SearchApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/SearchApiHandler.cs index 0b38167..dc8a576 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/SearchApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/SearchApiHandler.cs @@ -32,13 +32,13 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { IList public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Ensure an event is present - if (!uri.Parameters.ContainsKey("event")) { + if (!uri.Parameters.TryGetValue("event", out string eventParam)) { processor.WriteJson(new StatsResponse("Please specify an event parameter with comma separated list of events")); return; } // Split events into id, stat type, UNIX timestamp triples - string[] events = uri.Parameters["event"].Split(','); + string[] events = eventParam.Split(','); // Ensure data sent if (events.Length < 1) { diff --git a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs index 6061fdd..7983c97 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs @@ -67,8 +67,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { bool estimateContentLength = false; // Optionally estimate content length - if (uri.Parameters.ContainsKey("estimateContentLength")) { - estimateContentLength = uri.Parameters["estimateContentLength"].IsTrue(); + if (uri.Parameters.TryGetValue("estimateContentLength", out string estimateContentLengthParam)) { + estimateContentLength = estimateContentLengthParam.IsTrue(); } // Get the media item associated with this id @@ -93,8 +93,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // Optionally add isDirect parameter - if (uri.Parameters.ContainsKey("isDirect")) { - isDirect = uri.Parameters["isDirect"].IsTrue(); + if (uri.Parameters.TryGetValue("isDirect", out string isDirectParam)) { + isDirect = isDirectParam.IsTrue(); } if (seconds > 0) { @@ -147,8 +147,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Get the quality, default to medium uint quality = (uint)TranscodeQuality.Medium; - if (uri.Parameters.ContainsKey("transQuality")) { - string qualityString = uri.Parameters["transQuality"]; + if (uri.Parameters.TryGetValue("transQuality", out string qualityString)) { TranscodeQuality qualityEnum; uint qualityValue; // First try and parse a word enum value diff --git a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs index e9c75a0..6372aad 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs @@ -58,7 +58,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Generate the playlist file string response = null; - string[] transQualities = uri.Parameters.ContainsKey("transQuality") ? uri.Parameters["transQuality"].Split(',') : new string[] {"Medium"}; + string[] transQualities = uri.Parameters.TryGetValue("transQuality", out string transQualityParam) ? transQualityParam.Split(',') : new string[] {"Medium"}; if (transQualities.Length == 1) { // This is a single playlist response = this.GeneratePlaylist(item, transQualities[0], uri); @@ -86,8 +86,8 @@ private string GenerateMultiPlaylist(IMediaItem item, string[] transQualities, U // Grab URI parameters string s = uri.Parameters["s"]; string id = uri.Parameters["id"]; - string width = uri.Parameters.ContainsKey("width") ? uri.Parameters["width"] : null; - string height = uri.Parameters.ContainsKey("height") ? uri.Parameters["height"] : null; + string width = uri.Parameters.TryGetValue("width", out string widthParam) ? widthParam : null; + string height = uri.Parameters.TryGetValue("height", out string heightParam) ? heightParam : null; // Create new string, write M3U header StringBuilder builder = new StringBuilder(); @@ -142,8 +142,8 @@ private string GeneratePlaylist(IMediaItem item, string transQuality, UriWrapper // Set default parameters from URL string s = uri.Parameters["s"]; string id = uri.Parameters["id"]; - string width = uri.Parameters.ContainsKey("width") ? uri.Parameters["width"] : null; - string height = uri.Parameters.ContainsKey("height") ? uri.Parameters["height"] : null; + string width = uri.Parameters.TryGetValue("width", out string widthParam) ? widthParam : null; + string height = uri.Parameters.TryGetValue("height", out string heightParam) ? heightParam : null; // Begin creating M3U playlist StringBuilder builder = new StringBuilder(); diff --git a/WaveBox.Server/src/ApiHandler/Handlers/UsersApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/UsersApiHandler.cs index 0ba6036..e57fa88 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/UsersApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/UsersApiHandler.cs @@ -47,20 +47,20 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Parse common parameters // Username string username = null; - if (uri.Parameters.ContainsKey("username")) { - username = uri.Parameters["username"]; + if (uri.Parameters.TryGetValue("username", out string usernameParam)) { + username = usernameParam; } // Password string password = null; - if (uri.Parameters.ContainsKey("password")) { - password = uri.Parameters["password"]; + if (uri.Parameters.TryGetValue("password", out string passwordParam)) { + password = passwordParam; } // Role Role role = Role.User; int roleInt = 0; - if (uri.Parameters.ContainsKey("role") && Int32.TryParse(uri.Parameters["role"], out roleInt)) { + if (uri.Parameters.TryGetValue("role", out string roleParam) && Int32.TryParse(roleParam, out roleInt)) { // Validate role if (Enum.IsDefined(typeof(Role), roleInt)) { role = (Role)roleInt; @@ -68,11 +68,11 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // See if we need to make a test user - if (uri.Parameters.ContainsKey("testUser") && uri.Parameters["testUser"].IsTrue()) { + if (uri.Parameters.TryGetValue("testUser", out string testUserParam) && testUserParam.IsTrue()) { bool success = false; int durationSeconds = 0; - if (uri.Parameters.ContainsKey("durationSeconds")) { - success = Int32.TryParse(uri.Parameters["durationSeconds"], out durationSeconds); + if (uri.Parameters.TryGetValue("durationSeconds", out string durationSecondsParam)) { + success = Int32.TryParse(durationSecondsParam, out durationSeconds); } // Create a test user and reply with the account info @@ -107,13 +107,13 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { if (uri.Action == "killSession") { // Try to pull rowId from parameters for session management int rowId = 0; - if (!uri.Parameters.ContainsKey("rowId")) { + if (!uri.Parameters.TryGetValue("rowId", out string rowIdParam)) { processor.WriteJson(new UsersResponse("Missing parameter 'rowId' for action 'killSession'", null)); return; } // Try to parse rowId integer - if (!Int32.TryParse(uri.Parameters["rowId"], out rowId)) { + if (!Int32.TryParse(rowIdParam, out rowId)) { processor.WriteJson(new UsersResponse("Invalid integer for 'rowId' for action 'killSession'", null)); return; } diff --git a/WaveBox.Server/src/ApiHandler/Handlers/VideosApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/VideosApiHandler.cs index 971eec5..7a272f5 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/VideosApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/VideosApiHandler.cs @@ -33,8 +33,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { videos.Add(Injection.Get().VideoForId((int)uri.Id)); } // Check for a request for range of videos - else if (uri.Parameters.ContainsKey("range")) { - string[] range = uri.Parameters["range"].Split(','); + else if (uri.Parameters.TryGetValue("range", out string rangeParam)) { + string[] range = rangeParam.Split(','); // Ensure valid range was parsed if (range.Length != 2) { @@ -55,8 +55,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Check for a request to limit/paginate videos, like SQL // Note: can be combined with range or all videos - if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { - string[] limit = uri.Parameters["limit"].Split(','); + if (uri.Parameters.TryGetValue("limit", out string limitParam) && uri.Id == null) { + string[] limit = limitParam.Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { diff --git a/WaveBox.Server/src/ApiHandler/UriWrapper.cs b/WaveBox.Server/src/ApiHandler/UriWrapper.cs index be1abeb..568198f 100644 --- a/WaveBox.Server/src/ApiHandler/UriWrapper.cs +++ b/WaveBox.Server/src/ApiHandler/UriWrapper.cs @@ -52,8 +52,8 @@ public UriWrapper(string uriString, string httpMethod = null) { // Set action to read unless a valid one is found this.Action = "read"; - if (this.Parameters.ContainsKey("action")) { - this.Action = this.Parameters["action"]; + if (this.Parameters.TryGetValue("action", out string actionParam)) { + this.Action = actionParam; } // Check for RESTful HTTP method, and set action accordingly, overriding parameter action From e08f1c0ec58151db97c1b40308a54cd3db47e3d6 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:15:59 -0500 Subject: [PATCH 05/11] Fix CA1859/CA1822: concrete types and static members Declare locals, fields, and private helpers with their concrete types (List, Dictionary, HashSet, FileStream) instead of interfaces, and mark members that touch no instance state as static, updating call sites. The 3-arg IApiHandler.Process implementations stay instance methods; only the non-interface overloads and helpers changed. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- WaveBox.Core/src/Model/Folder.cs | 2 +- WaveBox.Core/src/Model/Playlist.cs | 6 ++--- WaveBox.Core/src/Model/User.cs | 2 +- .../src/Repository/AlbumArtistRepository.cs | 4 +-- .../src/Repository/FavoriteRepository.cs | 2 +- .../src/Repository/SessionRepository.cs | 2 +- WaveBox.Core/src/SQLiteNet.cs | 6 ++--- WaveBox.Server/src/Api/ApiDispatcher.cs | 25 +++++++++---------- .../src/Api/HttpContextProcessor.cs | 2 +- .../src/ApiHandler/ApiAuthenticate.cs | 2 +- .../ApiHandler/Handlers/DatabaseApiHandler.cs | 4 +-- .../ApiHandler/Handlers/ErrorApiHandler.cs | 4 +-- .../Handlers/PlaylistsApiHandler.cs | 20 +++++++-------- .../ApiHandler/Handlers/ScrobbleApiHandler.cs | 2 +- .../ApiHandler/Handlers/StatusApiHandler.cs | 4 +-- .../ApiHandler/Handlers/StreamApiHandler.cs | 2 +- .../Handlers/TranscodeHlsApiHandler.cs | 8 +++--- WaveBox.Server/src/ApiHandler/UriWrapper.cs | 4 +-- .../ArtistThumbnailDownloadOperation.cs | 2 +- .../src/FolderScanning/FolderScanOperation.cs | 10 ++++---- .../MusicBrainzScanOperation.cs | 16 ++++++------ WaveBox.Server/src/Service/ServiceManager.cs | 2 +- .../src/Service/Services/TranscodeService.cs | 2 +- WaveBox.Server/src/Static/Database.cs | 6 ++--- WaveBox.Server/src/Static/ServerSettings.cs | 2 +- .../Handlers/SubsonicMediaHandlers.cs | 2 +- .../Subsonic/Handlers/SubsonicUserHandlers.cs | 2 +- WaveBox.Server/src/Subsonic/SubsonicAuth.cs | 10 ++++---- WaveBox.Server/src/WaveBoxLifecycleService.cs | 4 +-- WaveBox.Server/src/WaveBoxMain.cs | 10 ++++---- tests/WaveBox.Server.Tests/PlaylistTests.cs | 4 +-- .../RepositorySmokeTests.cs | 4 +-- 32 files changed, 88 insertions(+), 89 deletions(-) diff --git a/WaveBox.Core/src/Model/Folder.cs b/WaveBox.Core/src/Model/Folder.cs index 228a2a7..6db92e2 100644 --- a/WaveBox.Core/src/Model/Folder.cs +++ b/WaveBox.Core/src/Model/Folder.cs @@ -54,7 +54,7 @@ public Folder ParentFolder() { return Injection.Get().FolderForId((int)ParentFolderId); } - public void Scan() { + public static void Scan() { // TO DO: scanning! yay! } diff --git a/WaveBox.Core/src/Model/Playlist.cs b/WaveBox.Core/src/Model/Playlist.cs index 08ebc65..657109b 100644 --- a/WaveBox.Core/src/Model/Playlist.cs +++ b/WaveBox.Core/src/Model/Playlist.cs @@ -152,7 +152,7 @@ public IList ListOfMediaItems() { conn = Injection.Get().GetSqliteConnection(); var result = conn.DeferredQuery("SELECT * FROM PlaylistItem WHERE PlaylistId = ? ORDER BY ItemPosition", PlaylistId); - IList items = new List(); + List items = new List(); foreach (PlaylistItem playlistItem in result) { if (!ReferenceEquals(playlistItem.ItemId, null)) { IMediaItem item = Injection.Get().MediaItemForId((int)playlistItem.ItemId); @@ -177,7 +177,7 @@ public void RemoveMediaItem(IMediaItem item) { } public void RemoveMediaItems(IList items) { - IList indexes = new List(); + List indexes = new List(); if (PlaylistId == 0 || items == null) { return; } @@ -356,7 +356,7 @@ public void AddMediaItem(int itemId, bool updateDatabase = true) { } public void AddMediaItems(IList itemIds) { - IList items = new List(); + List items = new List(); foreach (int itemId in itemIds) { logger.IfInfo("Checking item id " + itemId); IMediaItem item = Injection.Get().MediaItemForId(itemId); diff --git a/WaveBox.Core/src/Model/User.cs b/WaveBox.Core/src/Model/User.cs index f8fac5f..15f444f 100644 --- a/WaveBox.Core/src/Model/User.cs +++ b/WaveBox.Core/src/Model/User.cs @@ -60,7 +60,7 @@ public bool HasPermission(Role role) { return this.Role >= role ? true : false; } - public bool UpdateSession(string sessionId) { + public static bool UpdateSession(string sessionId) { // Update user's session based on its session ID Session s = Injection.Get().SessionForSessionId(sessionId); diff --git a/WaveBox.Core/src/Repository/AlbumArtistRepository.cs b/WaveBox.Core/src/Repository/AlbumArtistRepository.cs index c3a1d93..2c17066 100644 --- a/WaveBox.Core/src/Repository/AlbumArtistRepository.cs +++ b/WaveBox.Core/src/Repository/AlbumArtistRepository.cs @@ -156,11 +156,11 @@ public IList SinglesForAlbumArtistId(int albumArtistId) { try { conn = database.GetSqliteConnection(); - IList songs; + List songs; songs = conn.Query("SELECT ItemId FROM Song WHERE AlbumArtistId = ? AND AlbumId IS NULL", albumArtistId); if (songs.Count > 0) { - IList songIds = new List(); + List songIds = new List(); foreach (Song song in songs) { songIds.Add((int)song.ItemId); } diff --git a/WaveBox.Core/src/Repository/FavoriteRepository.cs b/WaveBox.Core/src/Repository/FavoriteRepository.cs index 5ae74e9..32cbb20 100644 --- a/WaveBox.Core/src/Repository/FavoriteRepository.cs +++ b/WaveBox.Core/src/Repository/FavoriteRepository.cs @@ -117,7 +117,7 @@ public IList ItemsForFavorites(IList favorites) { return null; } - IList items = new List(); + List items = new List(); foreach (Favorite fav in favorites) { switch (fav.FavoriteItemType) { case ItemType.AlbumArtist: diff --git a/WaveBox.Core/src/Repository/SessionRepository.cs b/WaveBox.Core/src/Repository/SessionRepository.cs index c490add..bb3e706 100644 --- a/WaveBox.Core/src/Repository/SessionRepository.cs +++ b/WaveBox.Core/src/Repository/SessionRepository.cs @@ -11,7 +11,7 @@ public class SessionRepository : ISessionRepository { private readonly IDatabase database; - private IDictionary Sessions { get; set; } + private Dictionary Sessions { get; set; } public SessionRepository(IDatabase database) { if (database == null) { diff --git a/WaveBox.Core/src/SQLiteNet.cs b/WaveBox.Core/src/SQLiteNet.cs index fa642a1..b416805 100644 --- a/WaveBox.Core/src/SQLiteNet.cs +++ b/WaveBox.Core/src/SQLiteNet.cs @@ -1720,7 +1720,7 @@ private Sqlite3Statement Prepare() { return stmt; } - private void Finalize(Sqlite3Statement stmt) { + private static void Finalize(Sqlite3Statement stmt) { SQLite3.Finalize(stmt); } @@ -2264,7 +2264,7 @@ private CompileResult CompileExpr(Expression expr, List queryArgs) { /// Compiles a BinaryExpression where one of the parameters is null. /// /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) { + private static string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) { if (expression.NodeType == ExpressionType.Equal) { return "(" + parameter.CommandText + " is ?)"; } else if (expression.NodeType == ExpressionType.NotEqual) { @@ -2274,7 +2274,7 @@ private string CompileNullBinaryExpression(BinaryExpression expression, CompileR expression.NodeType.ToString()); } - private string GetSqlName(Expression expr) { + private static string GetSqlName(BinaryExpression expr) { var n = expr.NodeType; if (n == ExpressionType.GreaterThan) { return ">"; diff --git a/WaveBox.Server/src/Api/ApiDispatcher.cs b/WaveBox.Server/src/Api/ApiDispatcher.cs index 1c3f8fc..8f8a11f 100644 --- a/WaveBox.Server/src/Api/ApiDispatcher.cs +++ b/WaveBox.Server/src/Api/ApiDispatcher.cs @@ -41,10 +41,10 @@ public async Task ProcessAsync(HttpContext context) { // Handlers are synchronous and may block (e.g. tailing a running transcode), so run the // legacy dispatch on a worker thread rather than tying up the request loop synchronously. - await Task.Run(() => this.Dispatch(context, rawUrl, method), context.RequestAborted); + await Task.Run(() => Dispatch(context, rawUrl, method), context.RequestAborted); } - private void Dispatch(HttpContext context, string rawUrl, string method) { + private static void Dispatch(HttpContext context, string rawUrl, string method) { UriWrapper uri = new UriWrapper(rawUrl, method); HttpContextProcessor processor = new HttpContextProcessor(context); @@ -63,7 +63,7 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { // Check for valid API action ("web" and "error" are technically valid, but can't be used in this way) if (uri.ApiAction == null || uri.ApiAction == "web" || uri.ApiAction == "error") { - this.WriteError(uri, processor, apiUser, "Invalid API call"); + WriteError(uri, processor, apiUser, "Invalid API call"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, rawUrl)); return; } @@ -71,7 +71,7 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { // Check for session cookie authentication, unless this is a login request string sessionId = null; if (uri.ApiAction != "login") { - sessionId = this.GetSessionCookie(processor); + sessionId = GetSessionCookie(processor); apiUser = Injection.Get().AuthenticateSession(sessionId); } @@ -81,7 +81,7 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { // If user still null, failed authentication, so serve error if (apiUser == null) { - this.WriteError(uri, processor, apiUser, "Authentication failed"); + WriteError(uri, processor, apiUser, "Authentication failed"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, rawUrl)); return; } @@ -89,7 +89,7 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { // apiUser.SessionId will be generated on new login, so that takes precedence for new session cookie apiUser.SessionId = apiUser.SessionId ?? sessionId; - this.SetSessionCookie(processor, apiUser.SessionId); + SetSessionCookie(processor, apiUser.SessionId); // Store user's current session object apiUser.CurrentSession = Injection.Get().SessionForSessionId(apiUser.SessionId); @@ -99,7 +99,7 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { // Check for valid API action if (apiHandler == null) { - this.WriteError(uri, processor, apiUser, "Invalid API call"); + WriteError(uri, processor, apiUser, "Invalid API call"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, rawUrl)); return; } @@ -109,7 +109,7 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { // Check if user has appropriate permissions for this action on this API handler if (!apiHandler.CheckPermission(apiUser, uri.Action)) { - this.WriteError(uri, processor, apiUser, "Permission denied"); + WriteError(uri, processor, apiUser, "Permission denied"); return; } @@ -117,13 +117,12 @@ private void Dispatch(HttpContext context, string rawUrl, string method) { apiHandler.Process(uri, processor, apiUser); } - private void WriteError(UriWrapper uri, HttpContextProcessor processor, User user, string message) { - ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Get().CreateApiHandler("error"); - errorApi.Process(uri, processor, user, message); + private static void WriteError(UriWrapper uri, HttpContextProcessor processor, User user, string message) { + ErrorApiHandler.Process(uri, processor, user, message); } // If a cookie is found, grab it and use it for authentication (legacy naive parsing, bug-compatible) - private string GetSessionCookie(HttpContextProcessor processor) { + private static string GetSessionCookie(HttpContextProcessor processor) { if (processor.HttpHeaders.ContainsKey("Cookie")) { // Split each cookie into pairs string[] cookies = processor.HttpHeaders["Cookie"].ToString().Split(new[] { ';', ',', '=' }, StringSplitOptions.RemoveEmptyEntries); @@ -141,7 +140,7 @@ private string GetSessionCookie(HttpContextProcessor processor) { } // Set a new session cookie to be set when the HTTP response is sent - private void SetSessionCookie(HttpContextProcessor processor, string sessionId) { + private static void SetSessionCookie(HttpContextProcessor processor, string sessionId) { if (sessionId != null) { // Calculate session timeout time (DateTime.UtcNow UTC + SessionTimeout minutes) DateTime expire = DateTime.UtcNow.ToUniversalTime().AddMinutes(Injection.Get().SessionTimeout); diff --git a/WaveBox.Server/src/Api/HttpContextProcessor.cs b/WaveBox.Server/src/Api/HttpContextProcessor.cs index ba9a8ec..1c959b7 100644 --- a/WaveBox.Server/src/Api/HttpContextProcessor.cs +++ b/WaveBox.Server/src/Api/HttpContextProcessor.cs @@ -234,7 +234,7 @@ public void WriteFile(Stream fs, int startOffset, long length, string mimeType, } } - private DateTime CleanLastModified(DateTime? lastModified) { + private static DateTime CleanLastModified(DateTime? lastModified) { // If null, use current time if (lastModified == null) { return DateTime.UtcNow; diff --git a/WaveBox.Server/src/ApiHandler/ApiAuthenticate.cs b/WaveBox.Server/src/ApiHandler/ApiAuthenticate.cs index 4165dec..4705c38 100644 --- a/WaveBox.Server/src/ApiHandler/ApiAuthenticate.cs +++ b/WaveBox.Server/src/ApiHandler/ApiAuthenticate.cs @@ -20,7 +20,7 @@ public User AuthenticateSession(string session) { User user = Injection.Get().UserForId((int)userId); if (user != null) { // Update this user's session and return - user.UpdateSession(session); + User.UpdateSession(session); return user; } } diff --git a/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs index 558e5fa..69a1c36 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs @@ -46,7 +46,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { try { // Read in entire database file - Stream stream = new FileStream(ServerUtility.RootPath() + databaseFileName, FileMode.Open, FileAccess.Read); + FileStream stream = new FileStream(ServerUtility.RootPath() + databaseFileName, FileMode.Open, FileAccess.Read); long length = stream.Length; int startOffset = 0; @@ -59,7 +59,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // We send the last query id as a custom header - IDictionary customHeader = new Dictionary(); + Dictionary customHeader = new Dictionary(); customHeader["WaveBox-LastQueryId"] = databaseLastQueryId.ToString(); // Send the database file diff --git a/WaveBox.Server/src/ApiHandler/Handlers/ErrorApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/ErrorApiHandler.cs index d3df5cb..6e1777e 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/ErrorApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/ErrorApiHandler.cs @@ -24,13 +24,13 @@ public bool CheckPermission(User user, string action) { /// Overload for IApiHandler interface /// public void Process(UriWrapper uri, IHttpProcessor processor, User user) { - this.Process(uri, processor, user, "Invalid API call"); + Process(uri, processor, user, "Invalid API call"); } /// /// Process logs the error, creates a JSON response, and send it back to the user on bad API call /// - public void Process(UriWrapper uri, IHttpProcessor processor, User user, string error) { + public static void Process(UriWrapper uri, IHttpProcessor processor, User user, string error) { logger.Error(error); ErrorResponse response = new ErrorResponse(error); diff --git a/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs index be86320..6e42207 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/PlaylistsApiHandler.cs @@ -73,7 +73,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { playlist.CreatePlaylist(); // Try to get the itemIds to add them to the playlist if necessary - IList itemIds = this.ParseItemIds(uri); + List itemIds = ParseItemIds(uri); if (itemIds.Count > 0) { playlist.AddMediaItems(itemIds); } @@ -113,7 +113,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // add - add items to a playlist if (uri.Action == "add") { // Try to get the itemIds to add them to the playlist if necessary - IList itemIds = this.ParseItemIds(uri); + List itemIds = ParseItemIds(uri); if (itemIds.Count == 0) { processor.WriteJson(new PlaylistsResponse("No item IDs found in URL", null, null, null)); return; @@ -174,8 +174,8 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // insert - insert item in playlist at specified index if (uri.Action == "insert") { - IList insertItemIds = this.ParseItemIds(uri); - IList insertIndexes = this.ParseIndexes(uri); + List insertItemIds = ParseItemIds(uri); + List insertIndexes = ParseIndexes(uri); if (insertItemIds.Count == 0 || insertItemIds.Count != insertIndexes.Count) { processor.WriteJson(new PlaylistsResponse("Incorrect number of items and indices supplied for action 'insert'", null, null, null)); return; @@ -198,7 +198,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // move - move an item in the playlist if (uri.Action == "move") { - IList moveIndexes = this.ParseIndexes(uri); + List moveIndexes = ParseIndexes(uri); if (moveIndexes.Count == 0 || moveIndexes.Count % 2 != 0) { processor.WriteJson(new PlaylistsResponse("Incorrect number of indices supplied for action 'move'", null, null, null)); return; @@ -220,7 +220,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // remove - remove items from playlist if (uri.Action == "remove") { - IList removeIndexes = this.ParseIndexes(uri); + List removeIndexes = ParseIndexes(uri); if (removeIndexes.Count == 0) { processor.WriteJson(new PlaylistsResponse("No indices supplied for action 'remove'", null, null, null)); return; @@ -240,9 +240,9 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { return; } - private IList ParseItemIds(UriWrapper uri) { + private static List ParseItemIds(UriWrapper uri) { // Try to get the itemIds - IList itemIds = new List(); + List itemIds = new List(); if (uri.Parameters.TryGetValue("itemIds", out string itemIdsParam)) { string[] itemIdStrings = itemIdsParam.Split(','); @@ -257,9 +257,9 @@ private IList ParseItemIds(UriWrapper uri) { return itemIds; } - private IList ParseIndexes(UriWrapper uri) { + private static List ParseIndexes(UriWrapper uri) { // Try to get the itemIds - IList itemIds = new List(); + List itemIds = new List(); if (uri.Parameters.TryGetValue("indexes", out string indexesParam)) { string[] itemIdStrings = indexesParam.Split(','); diff --git a/WaveBox.Server/src/ApiHandler/Handlers/ScrobbleApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/ScrobbleApiHandler.cs index 6fec581..56d17ca 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/ScrobbleApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/ScrobbleApiHandler.cs @@ -52,7 +52,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // Create list of scrobble data - IList scrobbles = new List(); + List scrobbles = new List(); // Get Last.fm API enumerations LfmScrobbleType scrobbleType = Lastfm.ScrobbleTypeForString(uri.Action); diff --git a/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs index 9266f68..563cad4 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs @@ -47,7 +47,7 @@ public bool CheckPermission(User user, string action) { public void Process(UriWrapper uri, IHttpProcessor processor, User user) { try { // Allocate an array of various statistics about the running process - IDictionary status = new Dictionary(); + Dictionary status = new Dictionary(); // Gather data about WaveBox process global::System.Diagnostics.Process proc = global::System.Diagnostics.Process.GetCurrentProcess(); @@ -140,7 +140,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { /// Returns the CPU usage of the WaveBox process at this instant in time, as a percentage of /// total machine capacity (PerformanceCounter was Windows-only, so this samples process time instead) /// - private float CpuUsage() { + private static float CpuUsage() { using (System.Diagnostics.Process process = System.Diagnostics.Process.GetCurrentProcess()) { TimeSpan startCpuTime = process.TotalProcessorTime; Stopwatch wallClock = Stopwatch.StartNew(); diff --git a/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs index 3742461..0b93928 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs @@ -64,7 +64,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } // Prepare file stream - Stream stream = item.File(); + FileStream stream = item.File(); long length = stream.Length; int startOffset = 0; long? limitToSize = null; diff --git a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs index 6372aad..8cff94b 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeHlsApiHandler.cs @@ -61,10 +61,10 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { string[] transQualities = uri.Parameters.TryGetValue("transQuality", out string transQualityParam) ? transQualityParam.Split(',') : new string[] {"Medium"}; if (transQualities.Length == 1) { // This is a single playlist - response = this.GeneratePlaylist(item, transQualities[0], uri); + response = GeneratePlaylist(item, transQualities[0], uri); } else { // This is a multi playlist - response = this.GenerateMultiPlaylist(item, transQualities, uri); + response = GenerateMultiPlaylist(item, transQualities, uri); } processor.WriteText(response, "application/x-mpegURL"); @@ -77,7 +77,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { /// /// Generates multiple item playlist /// - private string GenerateMultiPlaylist(IMediaItem item, string[] transQualities, UriWrapper uri) { + private static string GenerateMultiPlaylist(IMediaItem item, string[] transQualities, UriWrapper uri) { // Ensure duration is set if ((object)item.Duration == null) { return null; @@ -133,7 +133,7 @@ private string GenerateMultiPlaylist(IMediaItem item, string[] transQualities, U /// /// Generate playlist for a single item /// - private string GeneratePlaylist(IMediaItem item, string transQuality, UriWrapper uri) { + private static string GeneratePlaylist(IMediaItem item, string transQuality, UriWrapper uri) { // If duration not set, null! if ((object)item.Duration == null) { return null; diff --git a/WaveBox.Server/src/ApiHandler/UriWrapper.cs b/WaveBox.Server/src/ApiHandler/UriWrapper.cs index 568198f..e52cafc 100644 --- a/WaveBox.Server/src/ApiHandler/UriWrapper.cs +++ b/WaveBox.Server/src/ApiHandler/UriWrapper.cs @@ -119,8 +119,8 @@ private void ParseParameters() { /// /// Purge the empty elements in an array of strings, returning a list of strings /// - private IList RemoveEmptyElements(string[] input) { - IList result = new List(); + private static List RemoveEmptyElements(string[] input) { + List result = new List(); foreach (string s in input) { if (s != null && s != "") { diff --git a/WaveBox.Server/src/FolderScanning/ArtistThumbnailDownloadOperation.cs b/WaveBox.Server/src/FolderScanning/ArtistThumbnailDownloadOperation.cs index 4772ba2..fdfb4a8 100644 --- a/WaveBox.Server/src/FolderScanning/ArtistThumbnailDownloadOperation.cs +++ b/WaveBox.Server/src/FolderScanning/ArtistThumbnailDownloadOperation.cs @@ -34,7 +34,7 @@ public override void Start() { } // Keep a set of all MusicBrainz IDs known to WaveBox - ISet musicBrainzIds = new HashSet(); + HashSet musicBrainzIds = new HashSet(); // Find artists and album artists missing art IArtistRepository artistRepository = Injection.Get(); diff --git a/WaveBox.Server/src/FolderScanning/FolderScanOperation.cs b/WaveBox.Server/src/FolderScanning/FolderScanOperation.cs index c4b81a1..be6ea04 100644 --- a/WaveBox.Server/src/FolderScanning/FolderScanOperation.cs +++ b/WaveBox.Server/src/FolderScanning/FolderScanOperation.cs @@ -399,7 +399,7 @@ public Song CreateSong(string filePath, int? folderId, TagLib.File file) { return song; } - private bool ArtFileNeedsUpdating(string filePath) { + private static bool ArtFileNeedsUpdating(string filePath) { if (filePath == null) { return false; } @@ -431,7 +431,7 @@ private bool ArtFileNeedsUpdating(string filePath) { } // used for getting art from a file. - private Art CreateArt(string filePath) { + private static Art CreateArt(string filePath) { FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); // compute the hash of the file stream @@ -455,7 +455,7 @@ private Art CreateArt(string filePath) { // used for getting art from a tag. // We don't set the FilePath here, because that is only used for actual art files on disk - private Art CreateArt(TagLib.File file) { + private static Art CreateArt(TagLib.File file) { Art art = new Art(); if (file.Tag.Pictures.Length > 0) { @@ -494,7 +494,7 @@ private bool FileNeedsUpdating(string filePath, int? folderId, out bool isNew, o return needsUpdating; } - private bool VideoNeedsUpdating(string filePath, int? folderId, out bool isNew, out int? itemId) { + private static bool VideoNeedsUpdating(string filePath, int? folderId, out bool isNew, out int? itemId) { string fileName = Path.GetFileName(filePath); long lastModified = System.IO.File.GetLastWriteTime(filePath).ToUnixTime(); bool needsUpdating = true; @@ -557,7 +557,7 @@ public bool SongNeedsUpdating(string filePath, int? folderId, out bool isNew, ou return needsUpdating; } - private Video CreateVideo(string filePath, int? folderId, TagLib.File file) { + private static Video CreateVideo(string filePath, int? folderId, TagLib.File file) { int? itemId = Injection.Get().GenerateItemId(ItemType.Video); if (itemId == null) { return new Video(); diff --git a/WaveBox.Server/src/FolderScanning/MusicBrainzScanOperation.cs b/WaveBox.Server/src/FolderScanning/MusicBrainzScanOperation.cs index d81d068..6d72c53 100644 --- a/WaveBox.Server/src/FolderScanning/MusicBrainzScanOperation.cs +++ b/WaveBox.Server/src/FolderScanning/MusicBrainzScanOperation.cs @@ -32,10 +32,10 @@ public override void Start() { Stopwatch testAlbumArtistScanTime = new Stopwatch(); // Dictionary of artists and existing IDs - IDictionary existingIds = new Dictionary(); + Dictionary existingIds = new Dictionary(); // List of artists who don't have IDs - IList artistsMissingId = new List(); + List artistsMissingId = new List(); logger.IfInfo("------------- MUSICBRAINZ SCAN -------------"); @@ -52,7 +52,7 @@ public override void Start() { } } - IList albumArtistsMissingId = new List(); + List albumArtistsMissingId = new List(); IAlbumArtistRepository albumArtistRepository = Injection.Get(); IList allAlbumArtists = albumArtistRepository.AllAlbumArtists(); @@ -85,7 +85,7 @@ public override void Start() { logger.IfInfo("---------------------------------------------"); } - private string MusicBrainzIdForArtistName(string artistName) { + private static string MusicBrainzIdForArtistName(string artistName) { if (artistName == null) { return null; } @@ -134,7 +134,7 @@ private string MusicBrainzIdForArtistName(string artistName) { return null; } - private int ScanArtists(IDictionary existingIds, IList artistsMissingId) { + private int ScanArtists(Dictionary existingIds, IList artistsMissingId) { if (isRestart) { return 0; } @@ -153,7 +153,7 @@ private int ScanArtists(IDictionary existingIds, IList a // If ID not found, try to fetch it if (musicBrainzId == null) { - musicBrainzId = this.MusicBrainzIdForArtistName(artist.ArtistName); + musicBrainzId = MusicBrainzIdForArtistName(artist.ArtistName); } if (musicBrainzId != null) { @@ -173,7 +173,7 @@ private int ScanArtists(IDictionary existingIds, IList a return count; } - private int ScanAlbumArtists(IDictionary existingIds, IList albumArtistsMissingId) { + private int ScanAlbumArtists(Dictionary existingIds, IList albumArtistsMissingId) { if (isRestart) { return 0; } @@ -192,7 +192,7 @@ private int ScanAlbumArtists(IDictionary existingIds, IList Services = new List(); + private static List Services = new List(); /// /// Add a new service, by name, to the manager, optionally starting it automatically diff --git a/WaveBox.Server/src/Service/Services/TranscodeService.cs b/WaveBox.Server/src/Service/Services/TranscodeService.cs index ea29cff..4efb39d 100644 --- a/WaveBox.Server/src/Service/Services/TranscodeService.cs +++ b/WaveBox.Server/src/Service/Services/TranscodeService.cs @@ -18,7 +18,7 @@ public class TranscodeService : IService { public bool Running { get; set; } - private IList transcoders = new List(); + private List transcoders = new List(); public TranscodeService() { } diff --git a/WaveBox.Server/src/Static/Database.cs b/WaveBox.Server/src/Static/Database.cs index df8d471..4b4f0de 100644 --- a/WaveBox.Server/src/Static/Database.cs +++ b/WaveBox.Server/src/Static/Database.cs @@ -86,7 +86,7 @@ private void ApplyMigrations() { /// tables rather than for the file means an empty database left behind by an earlier /// connection still gets its schema, and makes repeat calls a no-op. /// - private void ApplySchemaIfEmpty(string name, string schemaPath, Func open, Action close) { + private static void ApplySchemaIfEmpty(string name, string schemaPath, Func open, Action close) { ISQLiteConnection conn = null; try { conn = open(); @@ -119,7 +119,7 @@ public ISQLiteConnection GetSqliteConnection() { if (isPoolingEnabled) { return mainPool.GetSqliteConnection(); } else { - ISQLiteConnection conn = new SQLite.SQLiteConnection(DatabasePath); + SQLite.SQLiteConnection conn = new SQLite.SQLiteConnection(DatabasePath); conn.Execute("PRAGMA synchronous = OFF"); // Five second busy timeout conn.BusyTimeout = new TimeSpan(0, 0, 5); @@ -139,7 +139,7 @@ public ISQLiteConnection GetQueryLogSqliteConnection() { if (isPoolingEnabled) { return logPool.GetSqliteConnection(); } else { - ISQLiteConnection conn = new SQLite.SQLiteConnection(QuerylogPath); + SQLite.SQLiteConnection conn = new SQLite.SQLiteConnection(QuerylogPath); conn.Execute("PRAGMA synchronous = OFF"); // Five second busy timeout conn.BusyTimeout = new TimeSpan(0, 0, 5); diff --git a/WaveBox.Server/src/Static/ServerSettings.cs b/WaveBox.Server/src/Static/ServerSettings.cs index 68a2b95..4bf37f5 100644 --- a/WaveBox.Server/src/Static/ServerSettings.cs +++ b/WaveBox.Server/src/Static/ServerSettings.cs @@ -302,7 +302,7 @@ private void PrepareMediaFolders() { } } - private Folder CreateFolder(string path, bool mediafolder) { + private static Folder CreateFolder(string path, bool mediafolder) { if (path == null || path == "") { // No path so just return a folder return new Folder(); diff --git a/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs b/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs index def3dc4..bd01c8d 100644 --- a/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs +++ b/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs @@ -99,7 +99,7 @@ private static void StreamOrDownload(SubsonicRequest req, HttpContextProcessor p // Direct byte-for-byte file streaming with Range support (same semantics as /api/stream) private static void SendDirect(SubsonicRequest req, HttpContextProcessor processor, IMediaItem item) { try { - System.IO.Stream stream = item.File(); + System.IO.FileStream stream = item.File(); long length = stream.Length; int startOffset = 0; long? limitToSize = null; diff --git a/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs b/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs index 2ddc651..2893d12 100644 --- a/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs +++ b/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs @@ -143,7 +143,7 @@ public static void DeleteUser(SubsonicRequest req, HttpContextProcessor processo SubsonicWriter.Write(req, processor, SubsonicWriter.Body()); } - private static IList MediaFolderIds() { + private static List MediaFolderIds() { return Injection.Get().MediaFolders() .Where(f => f.FolderId != null) .Select(f => (int)f.FolderId) diff --git a/WaveBox.Server/src/Subsonic/SubsonicAuth.cs b/WaveBox.Server/src/Subsonic/SubsonicAuth.cs index f012dd6..1db5dc0 100644 --- a/WaveBox.Server/src/Subsonic/SubsonicAuth.cs +++ b/WaveBox.Server/src/Subsonic/SubsonicAuth.cs @@ -46,13 +46,13 @@ public User Authenticate(SubsonicRequest req, out SubsonicError error) { return null; } - User keyUser = this.UserForApiKey(apiKey); + User keyUser = UserForApiKey(apiKey); if (keyUser == null) { error = new SubsonicError { Code = SubsonicError.InvalidApiKey, Message = "Invalid API key" }; return null; } - return this.Authenticated(keyUser, req); + return Authenticated(keyUser, req); } if (token != null || salt != null) { @@ -96,7 +96,7 @@ public User Authenticate(SubsonicRequest req, out SubsonicError error) { // Cache the successful verification with a sliding expiry this.verified[username] = new VerifiedAuth { PasswordSha256 = presented, Expires = DateTime.UtcNow + CacheTtl }; - return this.Authenticated(user, req); + return Authenticated(user, req); } // Drop a user's cached verification (call after password change, user update, or delete) @@ -107,7 +107,7 @@ public void Evict(string username) { } } - private User UserForApiKey(string apiKey) { + private static User UserForApiKey(string apiKey) { if (String.IsNullOrEmpty(apiKey)) { return null; } @@ -128,7 +128,7 @@ private User UserForApiKey(string apiKey) { // Repository users are shared cache instances; hand each request its own copy so the // synthesized session (and its client name) can't leak across concurrent requests - private User Authenticated(User user, SubsonicRequest req) { + private static User Authenticated(User user, SubsonicRequest req) { return new User { UserId = user.UserId, UserName = user.UserName, diff --git a/WaveBox.Server/src/WaveBoxLifecycleService.cs b/WaveBox.Server/src/WaveBoxLifecycleService.cs index 06ed60c..8ea42bd 100644 --- a/WaveBox.Server/src/WaveBoxLifecycleService.cs +++ b/WaveBox.Server/src/WaveBoxLifecycleService.cs @@ -40,7 +40,7 @@ public Task StartAsync(CancellationToken cancellationToken) { Core.Injection.Get().Initialize(); this.wavebox = new WaveBoxMain(); - this.wavebox.Start(); + WaveBoxMain.Start(); logger.IfInfo("Started!"); return Task.CompletedTask; @@ -62,7 +62,7 @@ public Task StopAsync(CancellationToken cancellationToken) { // Stop the server if (this.wavebox != null) { - this.wavebox.Stop(); + WaveBoxMain.Stop(); this.wavebox = null; } diff --git a/WaveBox.Server/src/WaveBoxMain.cs b/WaveBox.Server/src/WaveBoxMain.cs index b22c05d..0fae068 100644 --- a/WaveBox.Server/src/WaveBoxMain.cs +++ b/WaveBox.Server/src/WaveBoxMain.cs @@ -27,7 +27,7 @@ class WaveBoxMain { /// The main instance of WaveBox which runs the server. Creates necessary directories, initializes /// database and settings, and starts all associated services. /// - public void Start() { + public static void Start() { logger.IfInfo("Initializing WaveBox " + ServerInfo.BuildVersion + " on " + ServerInfo.OS.ToDescription() + " platform..."); // Create directory for WaveBox's root path, if it doesn't exist @@ -84,7 +84,7 @@ public void Start() { /// /// Stop the WaveBox main /// - public void Stop() { + public static void Stop() { // Stop all running services ServiceManager.StopAll(); ServiceManager.Clear(); @@ -93,9 +93,9 @@ public void Stop() { /// /// Restart the WaveBox main /// - public void Restart() { - this.Stop(); - this.Start(); + public static void Restart() { + Stop(); + Start(); } } } diff --git a/tests/WaveBox.Server.Tests/PlaylistTests.cs b/tests/WaveBox.Server.Tests/PlaylistTests.cs index e1b2384..e61b036 100644 --- a/tests/WaveBox.Server.Tests/PlaylistTests.cs +++ b/tests/WaveBox.Server.Tests/PlaylistTests.cs @@ -10,7 +10,7 @@ namespace WaveBox.Server.Tests { [Collection("Integration")] public class PlaylistTests : IDisposable { private readonly IntegrationHarness harness; - private readonly IList songs; + private readonly List songs; public PlaylistTests() { harness = new IntegrationHarness(); @@ -29,7 +29,7 @@ private static Playlist Create(string name) { return playlist; } - private static IList ItemIds(Playlist playlist) { + private static List ItemIds(Playlist playlist) { return playlist.ListOfMediaItems().Select(i => i.ItemId).ToList(); } diff --git a/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs b/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs index 7c7b1c2..6710aef 100644 --- a/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs +++ b/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs @@ -12,7 +12,7 @@ namespace WaveBox.Server.Tests { [Collection("Integration")] public class RepositorySmokeTests : IDisposable { private readonly IntegrationHarness harness; - private readonly IList songs; + private readonly List songs; public RepositorySmokeTests() { harness = new IntegrationHarness(); @@ -45,7 +45,7 @@ public void AlbumRepositoryAggregatesScannedSongs() { Assert.Equal("Test Album", album.AlbumName); Assert.Equal(album.AlbumName, repo.AlbumForId((int)album.AlbumId).AlbumName); - IDictionary counts = repo.SongCountsByAlbum().ToDictionary(c => (int)c.GroupId); + Dictionary counts = repo.SongCountsByAlbum().ToDictionary(c => (int)c.GroupId); Assert.Equal(3, counts[(int)album.AlbumId].Count); } From 9423bb0e47433e4d15188274f9764e34c59798cb Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:18:36 -0500 Subject: [PATCH 06/11] Fix newly surfaced bug-risk warnings in WaveBox.Core A full rebuild surfaced analyzer warnings that incremental builds had hidden. The behavior-relevant ones: - CA2013: more nullable value types passed to ReferenceEquals in Playlist, User, and UserRepository; use plain null comparisons. - CA2208: FavoritesForAlbumArtistId threw ArgumentNullException with the wrong parameter name ("artistId", a copy-paste from the artist overload), and InsertTypeExtensions.QueryText passed the type name instead of the parameter name; both now use nameof. - CA1873: Log.Write now checks ILogger.IsEnabled before logging so disabled levels skip message formatting. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- WaveBox.Core/src/BaseClasses.cs | 2 +- WaveBox.Core/src/Logging/Log.cs | 4 +++- WaveBox.Core/src/Model/Playlist.cs | 10 +++++----- WaveBox.Core/src/Model/User.cs | 2 +- WaveBox.Core/src/Repository/FavoriteRepository.cs | 2 +- WaveBox.Core/src/Repository/UserRepository.cs | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/WaveBox.Core/src/BaseClasses.cs b/WaveBox.Core/src/BaseClasses.cs index 2e67a64..fd03290 100644 --- a/WaveBox.Core/src/BaseClasses.cs +++ b/WaveBox.Core/src/BaseClasses.cs @@ -197,7 +197,7 @@ public static string QueryText(this InsertType insertType) { case InsertType.Replace: return "REPLACE"; default: - throw new ArgumentOutOfRangeException("InsertType"); + throw new ArgumentOutOfRangeException(nameof(insertType)); } } } diff --git a/WaveBox.Core/src/Logging/Log.cs b/WaveBox.Core/src/Logging/Log.cs index 5e75dbe..3999f97 100644 --- a/WaveBox.Core/src/Logging/Log.cs +++ b/WaveBox.Core/src/Logging/Log.cs @@ -72,7 +72,9 @@ private Microsoft.Extensions.Logging.ILogger Logger { private void Write(LogLevel level, object message, Exception exception) { Microsoft.Extensions.Logging.ILogger current = Logger; if (current != null) { - current.Log(level, exception, "{Message}", message); + if (current.IsEnabled(level)) { + current.Log(level, exception, "{Message}", message); + } } else { // Host not built yet; write straight to the console so early startup isn't silent Console.WriteLine(DateTime.Now.ToString("HH:mm:ss,fff") + " " + level + " " + category + " - " + message + (exception != null ? Environment.NewLine + exception : "")); diff --git a/WaveBox.Core/src/Model/Playlist.cs b/WaveBox.Core/src/Model/Playlist.cs index 657109b..7461b51 100644 --- a/WaveBox.Core/src/Model/Playlist.cs +++ b/WaveBox.Core/src/Model/Playlist.cs @@ -154,7 +154,7 @@ public IList ListOfMediaItems() { List items = new List(); foreach (PlaylistItem playlistItem in result) { - if (!ReferenceEquals(playlistItem.ItemId, null)) { + if (playlistItem.ItemId != null) { IMediaItem item = Injection.Get().MediaItemForId((int)playlistItem.ItemId); if (!ReferenceEquals(item, null)) { items.Add(item); @@ -376,7 +376,7 @@ public void InsertMediaItem(int itemId, int index) { public void InsertMediaItem(IMediaItem item, int index) { // make sure the input is within bounds and is not null - if (ReferenceEquals(item, null) || index > PlaylistCount || index < 0 || ReferenceEquals(PlaylistId, null)) { + if (ReferenceEquals(item, null) || index > PlaylistCount || index < 0 || PlaylistId == null) { return; } @@ -384,7 +384,7 @@ public void InsertMediaItem(IMediaItem item, int index) { try { int? id = Injection.Get().GenerateItemId(ItemType.PlaylistItem); - if (!ReferenceEquals(id, null)) { + if (id != null) { // to do - better way of knowing whether or not a query has been successfully completed. conn = Injection.Get().GetSqliteConnection(); conn.BeginTransaction(); @@ -446,13 +446,13 @@ public void ClearPlaylist() { } public void CreatePlaylist() { - if (ReferenceEquals(PlaylistId, null)) { + if (PlaylistId == null) { UpdateDatabase(); } } public void DeletePlaylist() { - if (ReferenceEquals(PlaylistId, null)) { + if (PlaylistId == null) { return; } diff --git a/WaveBox.Core/src/Model/User.cs b/WaveBox.Core/src/Model/User.cs index 15f444f..349f78b 100644 --- a/WaveBox.Core/src/Model/User.cs +++ b/WaveBox.Core/src/Model/User.cs @@ -224,7 +224,7 @@ public bool CreateSession(string password, string clientName) { } public bool Delete() { - if (ReferenceEquals(UserId, null)) { + if (UserId == null) { return true; } diff --git a/WaveBox.Core/src/Repository/FavoriteRepository.cs b/WaveBox.Core/src/Repository/FavoriteRepository.cs index 32cbb20..ad74512 100644 --- a/WaveBox.Core/src/Repository/FavoriteRepository.cs +++ b/WaveBox.Core/src/Repository/FavoriteRepository.cs @@ -104,7 +104,7 @@ public IList FavoritesForArtistId(int? artistId, int? userId) { public IList FavoritesForAlbumArtistId(int? albumArtistId, int? userId) { if (albumArtistId == null) { - throw new ArgumentNullException("artistId"); + throw new ArgumentNullException(nameof(albumArtistId)); } else if (userId == null) { throw new ArgumentNullException("userId"); } diff --git a/WaveBox.Core/src/Repository/UserRepository.cs b/WaveBox.Core/src/Repository/UserRepository.cs index fe75140..6b66270 100644 --- a/WaveBox.Core/src/Repository/UserRepository.cs +++ b/WaveBox.Core/src/Repository/UserRepository.cs @@ -101,7 +101,7 @@ public User CreateUser(string userName, string password, Role role, long? delete public User CreateTestUser(long? durationSeconds) { // Create a new user with random username and password, that lasts for the specified duration - if (ReferenceEquals(durationSeconds, null)) { + if (durationSeconds == null) { // If no duration specified, use 24 hours durationSeconds = 60 * 60 * 24; } From 73f28cd18b59e016ac709cb6baccd16d35014ff3 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:25:17 -0500 Subject: [PATCH 07/11] Fix micro-perf analyzer warnings across app and test code CA1861 constant array arguments become params Split/Trim calls or static readonly fields; CA2263 Enum.GetNames uses the generic overload; CA1846/CA1866/CA1834 switch to span, char, and Append(char) overloads; CA1840 uses Environment.CurrentManagedThreadId. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- WaveBox.Core/src/Extensions/IListExtensions.cs | 2 +- WaveBox.Core/src/Model/Playlist.cs | 2 +- WaveBox.Core/src/Static/ThreadSafeRandom.cs | 3 +-- WaveBox.Server/src/Api/ApiDispatcher.cs | 4 +++- WaveBox.Server/src/ApiHandler/ArtStream.cs | 2 +- .../src/ApiHandler/Handlers/DatabaseApiHandler.cs | 2 +- .../src/ApiHandler/Handlers/StatusApiHandler.cs | 4 ++-- .../src/ApiHandler/Handlers/StreamApiHandler.cs | 2 +- .../src/ApiHandler/Handlers/TranscodeApiHandler.cs | 2 +- .../src/ApiHandler/Handlers/WebApiHandler.cs | 2 +- WaveBox.Server/src/ApiHandler/UriWrapper.cs | 2 +- WaveBox.Server/src/Program.cs | 10 ++++++---- .../src/Subsonic/Handlers/SubsonicMediaHandlers.cs | 2 +- .../src/Subsonic/Handlers/SubsonicUserHandlers.cs | 2 +- WaveBox.Server/src/Subsonic/SubsonicAuth.cs | 2 +- tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs | 7 +++++-- tests/WaveBox.Server.Tests/ServerSettingsTests.cs | 4 +++- tests/WaveBox.Server.Tests/SubsonicMapperTests.cs | 7 +++++-- tests/WaveBox.Server.Tests/SubsonicRequestTests.cs | 4 +++- 19 files changed, 39 insertions(+), 26 deletions(-) diff --git a/WaveBox.Core/src/Extensions/IListExtensions.cs b/WaveBox.Core/src/Extensions/IListExtensions.cs index 83dd0d9..37be730 100644 --- a/WaveBox.Core/src/Extensions/IListExtensions.cs +++ b/WaveBox.Core/src/Extensions/IListExtensions.cs @@ -65,7 +65,7 @@ public static string ToCSV(this IList list, bool quoted = false) { } } - return buffer.Trim(new char[] {' ', ','}); + return buffer.Trim(' ', ','); } } } diff --git a/WaveBox.Core/src/Model/Playlist.cs b/WaveBox.Core/src/Model/Playlist.cs index 7461b51..3bcc288 100644 --- a/WaveBox.Core/src/Model/Playlist.cs +++ b/WaveBox.Core/src/Model/Playlist.cs @@ -61,7 +61,7 @@ public string CalculateHash() { foreach (PlaylistItem playlistItem in result) { itemIds.Append(playlistItem.ItemId); - itemIds.Append("|"); + itemIds.Append('|'); } } catch (Exception e) { logger.Error(e); diff --git a/WaveBox.Core/src/Static/ThreadSafeRandom.cs b/WaveBox.Core/src/Static/ThreadSafeRandom.cs index 8764e42..e3aa8ff 100644 --- a/WaveBox.Core/src/Static/ThreadSafeRandom.cs +++ b/WaveBox.Core/src/Static/ThreadSafeRandom.cs @@ -1,5 +1,4 @@ using System; -using System.Threading; namespace WaveBox.Core.Static { public static class ThreadSafeRandom { @@ -7,7 +6,7 @@ public static class ThreadSafeRandom { private static Random Local; public static Random ThisThreadsRandom { - get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); } + get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Environment.CurrentManagedThreadId))); } } } } diff --git a/WaveBox.Server/src/Api/ApiDispatcher.cs b/WaveBox.Server/src/Api/ApiDispatcher.cs index 8f8a11f..4be4dc9 100644 --- a/WaveBox.Server/src/Api/ApiDispatcher.cs +++ b/WaveBox.Server/src/Api/ApiDispatcher.cs @@ -18,6 +18,8 @@ namespace WaveBox.Api { public class ApiDispatcher { private static readonly WaveBox.Core.Logging.ILog logger = WaveBox.Core.Logging.LogManager.GetLogger(typeof(ApiDispatcher)); + private static readonly char[] cookieSplitChars = new char[] { ';', ',', '=' }; + public async Task ProcessAsync(HttpContext context) { string method = context.Request.Method.ToUpperInvariant(); @@ -125,7 +127,7 @@ private static void WriteError(UriWrapper uri, HttpContextProcessor processor, U private static string GetSessionCookie(HttpContextProcessor processor) { if (processor.HttpHeaders.ContainsKey("Cookie")) { // Split each cookie into pairs - string[] cookies = processor.HttpHeaders["Cookie"].ToString().Split(new[] { ';', ',', '=' }, StringSplitOptions.RemoveEmptyEntries); + string[] cookies = processor.HttpHeaders["Cookie"].ToString().Split(cookieSplitChars, StringSplitOptions.RemoveEmptyEntries); // Iterate all cookies for (int i = 0; i < cookies.Length - 1; i += 2) { diff --git a/WaveBox.Server/src/ApiHandler/ArtStream.cs b/WaveBox.Server/src/ApiHandler/ArtStream.cs index d5349c0..55f493e 100644 --- a/WaveBox.Server/src/ApiHandler/ArtStream.cs +++ b/WaveBox.Server/src/ApiHandler/ArtStream.cs @@ -125,7 +125,7 @@ public static bool FolderContainsImages(string dir, out string firstImageFoundPa foreach (string file in Directory.GetFiles(dir)) { ext = Path.GetExtension(file).ToLower(); - if (validImageExtensions.Contains(ext) && !Path.GetFileName(file).StartsWith(".")) { + if (validImageExtensions.Contains(ext) && !Path.GetFileName(file).StartsWith('.')) { firstImageFoundPath = file; } } diff --git a/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs index 69a1c36..2e30636 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/DatabaseApiHandler.cs @@ -53,7 +53,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Handle the Range header to start from later in the file if connection interrupted if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; - string start = range.Split(new char[] {'-', '='})[1]; + string start = range.Split('-', '=')[1]; logger.IfInfo("Connection retried. Resuming from " + start); startOffset = Convert.ToInt32(start); } diff --git a/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs index 563cad4..2a1dba3 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/StatusApiHandler.cs @@ -79,9 +79,9 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Get peak memory usage in MB status["peakMemoryMb"] = (float)proc.PeakWorkingSet64 / 1024f / 1024f; // Get list of media types WaveBox can index and serve (removing "Unknown") - status["mediaTypes"] = Enum.GetNames(typeof(FileType)).Where(x => x != "Unknown").ToList(); + status["mediaTypes"] = Enum.GetNames().Where(x => x != "Unknown").ToList(); // Get list of transcoders available - status["transcoders"] = Enum.GetNames(typeof(TranscodeType)).ToList(); + status["transcoders"] = Enum.GetNames().ToList(); // Get list of services status["services"] = ServiceManager.GetServices(); // Get last query log ID diff --git a/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs index 0b93928..8b6ac27 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/StreamApiHandler.cs @@ -80,7 +80,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } else if (processor.HttpHeaders.ContainsKey("Range")) { // Handle the Range header to start from later in the file string range = (string)processor.HttpHeaders["Range"]; - var split = range.Split(new char[] {'-', '='}); + var split = range.Split('-', '='); string start = split[1]; string end = split.Length > 2 ? split[2] : null; diff --git a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs index 7983c97..1c7d612 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/TranscodeApiHandler.cs @@ -109,7 +109,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { } else if (processor.HttpHeaders.ContainsKey("Range")) { // Handle the Range header to start from later in the file string range = (string)processor.HttpHeaders["Range"]; - var split = range.Split(new char[] {'-', '='}); + var split = range.Split('-', '='); string start = split[1]; string end = split.Length > 2 ? split[2] : null; logger.IfInfo("Range header: " + range + " Resuming from " + start + " end: " + end); diff --git a/WaveBox.Server/src/ApiHandler/Handlers/WebApiHandler.cs b/WaveBox.Server/src/ApiHandler/Handlers/WebApiHandler.cs index 23861ce..1b2e9d6 100644 --- a/WaveBox.Server/src/ApiHandler/Handlers/WebApiHandler.cs +++ b/WaveBox.Server/src/ApiHandler/Handlers/WebApiHandler.cs @@ -91,7 +91,7 @@ public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Handle the Range header to start from later in the file if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; - string start = range.Split(new char[] {'-', '='})[1]; + string start = range.Split('-', '=')[1]; logger.IfInfo("Connection retried. Resuming from " + start); startOffset = Convert.ToInt32(start); } diff --git a/WaveBox.Server/src/ApiHandler/UriWrapper.cs b/WaveBox.Server/src/ApiHandler/UriWrapper.cs index e52cafc..a2459af 100644 --- a/WaveBox.Server/src/ApiHandler/UriWrapper.cs +++ b/WaveBox.Server/src/ApiHandler/UriWrapper.cs @@ -104,7 +104,7 @@ private void ParseParameters() { if (UriString.Contains('?')) { // if we split the uri by the question mark, the second part of the split will be the params string parametersString = this.UriString.Split('?')[1]; - string[] splitParams = parametersString.Split(new char[] {'=', '&'}); + string[] splitParams = parametersString.Split('=', '&'); // Add parameters to the dictionary as we parse the parameters array for (int i = 0; i <= splitParams.Length - 2; i = i + 2) { diff --git a/WaveBox.Server/src/Program.cs b/WaveBox.Server/src/Program.cs index 6720ed6..5cf328b 100644 --- a/WaveBox.Server/src/Program.cs +++ b/WaveBox.Server/src/Program.cs @@ -14,6 +14,11 @@ namespace WaveBox { public static class Program { + private static readonly string[] compressedMimeTypes = new string[] { + "application/json", "text/html", "text/css", "text/plain", + "text/javascript", "application/javascript", "application/xml" + }; + // Kestrel's options are constructed during Build(), before the DI container or database are // ready, so the listen port is read here with a minimal, dependency-free parse of wavebox.conf. // The full settings load (which also touches the database) still happens in WaveBoxLifecycleService. @@ -65,10 +70,7 @@ public static void Main(string[] args) { // Standards-compliant gzip/deflate for text responses (replaces the hand-rolled negotiation) builder.Services.AddResponseCompression(options => { - options.MimeTypes = new[] { - "application/json", "text/html", "text/css", "text/plain", - "text/javascript", "application/javascript", "application/xml" - }; + options.MimeTypes = compressedMimeTypes; }); builder.WebHost.ConfigureKestrel(options => { diff --git a/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs b/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs index bd01c8d..da9719c 100644 --- a/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs +++ b/WaveBox.Server/src/Subsonic/Handlers/SubsonicMediaHandlers.cs @@ -106,7 +106,7 @@ private static void SendDirect(SubsonicRequest req, HttpContextProcessor process if (processor.HttpHeaders.ContainsKey("Range")) { string range = (string)processor.HttpHeaders["Range"]; - var split = range.Split(new char[] { '-', '=' }); + var split = range.Split('-', '='); string start = split[1]; string end = split.Length > 2 ? split[2] : null; diff --git a/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs b/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs index 2893d12..2b33190 100644 --- a/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs +++ b/WaveBox.Server/src/Subsonic/Handlers/SubsonicUserHandlers.cs @@ -154,7 +154,7 @@ private static List MediaFolderIds() { private static string DecodePassword(string password) { if (password != null && password.StartsWith("enc:", StringComparison.OrdinalIgnoreCase)) { try { - return Encoding.UTF8.GetString(Convert.FromHexString(password.Substring(4))); + return Encoding.UTF8.GetString(Convert.FromHexString(password.AsSpan(4))); } catch (FormatException) { return password; } diff --git a/WaveBox.Server/src/Subsonic/SubsonicAuth.cs b/WaveBox.Server/src/Subsonic/SubsonicAuth.cs index 1db5dc0..86af3e3 100644 --- a/WaveBox.Server/src/Subsonic/SubsonicAuth.cs +++ b/WaveBox.Server/src/Subsonic/SubsonicAuth.cs @@ -68,7 +68,7 @@ public User Authenticate(SubsonicRequest req, out SubsonicError error) { // Hex-encoded password variant: p=enc:48656c6c6f if (password.StartsWith("enc:", StringComparison.OrdinalIgnoreCase)) { try { - password = Encoding.UTF8.GetString(Convert.FromHexString(password.Substring(4))); + password = Encoding.UTF8.GetString(Convert.FromHexString(password.AsSpan(4))); } catch (FormatException) { error = new SubsonicError { Code = SubsonicError.WrongCredentials, Message = "Wrong username or password" }; return null; diff --git a/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs b/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs index 858ce32..edc072b 100644 --- a/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs +++ b/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs @@ -12,6 +12,9 @@ namespace WaveBox.Server.Tests { /// none of this depends on the bundled res/migrations content (which is empty today). /// public class DatabaseMigratorTests : IDisposable { + private static readonly int[] expectedNumericOrder = new int[] { 1, 2, 10 }; + private static readonly int[] expectedVersionsWithGap = new int[] { 1, 7 }; + private readonly string workDir; private readonly string migrationsDir; private readonly string dbPath; @@ -54,7 +57,7 @@ public void DiscoverOrdersNumericallyNotLexically() { IList found = DatabaseMigrator.Discover(migrationsDir); - Assert.Equal(new int[] { 1, 2, 10 }, found.Select(m => m.Version).ToArray()); + Assert.Equal(expectedNumericOrder, found.Select(m => m.Version).ToArray()); } [Fact] @@ -102,7 +105,7 @@ public void DiscoverAllowsGapsInNumbering() { WriteMigration("00001_one.sql", "SELECT 1;"); WriteMigration("00007_seven.sql", "SELECT 1;"); - Assert.Equal(new int[] { 1, 7 }, DatabaseMigrator.Discover(migrationsDir).Select(m => m.Version).ToArray()); + Assert.Equal(expectedVersionsWithGap, DatabaseMigrator.Discover(migrationsDir).Select(m => m.Version).ToArray()); } // --- Apply ---------------------------------------------------------- diff --git a/tests/WaveBox.Server.Tests/ServerSettingsTests.cs b/tests/WaveBox.Server.Tests/ServerSettingsTests.cs index 93bc88b..e3f6f0a 100644 --- a/tests/WaveBox.Server.Tests/ServerSettingsTests.cs +++ b/tests/WaveBox.Server.Tests/ServerSettingsTests.cs @@ -7,6 +7,8 @@ namespace WaveBox.Server.Tests { [Collection("Integration")] public class ServerSettingsTests : IDisposable { + private static readonly string[] expectedMediaFolders = new string[] { "/nonexistent" }; + private readonly IntegrationHarness harness; private readonly IServerSettings settings; @@ -47,7 +49,7 @@ public void ParseSettingsAcceptsCommentsAndTrailingCommas() { Assert.Equal(7777, settings.Port); Assert.Equal("dark", settings.Theme); - Assert.Equal(new[] { "/nonexistent" }, settings.MediaFolders); + Assert.Equal(expectedMediaFolders, settings.MediaFolders); } [Fact] diff --git a/tests/WaveBox.Server.Tests/SubsonicMapperTests.cs b/tests/WaveBox.Server.Tests/SubsonicMapperTests.cs index 93dd0a1..0976817 100644 --- a/tests/WaveBox.Server.Tests/SubsonicMapperTests.cs +++ b/tests/WaveBox.Server.Tests/SubsonicMapperTests.cs @@ -8,6 +8,9 @@ namespace WaveBox.Server.Tests { public class SubsonicMapperTests { + private static readonly string[] expectedLetterKeys = new string[] { "A", "B", "Z" }; + private static readonly string[] expectedHashBucketKeys = new string[] { "#", "M" }; + [Fact] public void Iso8601FormatsKnownValues() { Assert.Equal("1970-01-01T00:00:00Z", SubsonicMapper.Iso8601(0)); @@ -25,7 +28,7 @@ public void GroupByIndexBucketsByFirstLetter() { List>> groups = SubsonicMapper.GroupByIndex(names, s => s); - Assert.Equal(new[] { "A", "B", "Z" }, groups.Select(g => g.Key).ToArray()); + Assert.Equal(expectedLetterKeys, groups.Select(g => g.Key).ToArray()); Assert.Equal(new List { "apple", "Avocado" }, groups[0].Value); Assert.Equal(new List { "banana" }, groups[1].Value); } @@ -37,7 +40,7 @@ public void GroupByIndexPutsNonLettersInHashBucket() { List>> groups = SubsonicMapper.GroupByIndex(names, s => s); // '#' sorts before letters ordinally, so it comes first - Assert.Equal(new[] { "#", "M" }, groups.Select(g => g.Key).ToArray()); + Assert.Equal(expectedHashBucketKeys, groups.Select(g => g.Key).ToArray()); Assert.Equal(new List { "1999", "Éclair", "", null }, groups[0].Value); } diff --git a/tests/WaveBox.Server.Tests/SubsonicRequestTests.cs b/tests/WaveBox.Server.Tests/SubsonicRequestTests.cs index e573f18..a218dd6 100644 --- a/tests/WaveBox.Server.Tests/SubsonicRequestTests.cs +++ b/tests/WaveBox.Server.Tests/SubsonicRequestTests.cs @@ -7,6 +7,8 @@ namespace WaveBox.Server.Tests { public class SubsonicRequestTests { + private static readonly string[] formSongIds = new string[] { "2", "3" }; + private static SubsonicRequest Request(string queryString, Dictionary formValues = null) { DefaultHttpContext context = new DefaultHttpContext(); context.Request.QueryString = new QueryString(queryString); @@ -33,7 +35,7 @@ public void GetAllPreservesDuplicateKeys() { [Fact] public void GetAllMergesQueryThenForm() { SubsonicRequest req = Request("?songId=1", new Dictionary { - { "songId", new StringValues(new[] { "2", "3" }) } + { "songId", new StringValues(formSongIds) } }); Assert.Equal(new List { "1", "2", "3" }, req.GetAll("songId")); From 5c20152908bb0c3f7b1dad3b722c371f2c1592bd Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:25:26 -0500 Subject: [PATCH 08/11] Fix CA1510/CA1507: modernize repository null guards Constructor guards use ArgumentNullException.ThrowIfNull; the nullable int? parameter guards in FavoriteRepository keep explicit throws (to avoid CA1871 boxing) but use nameof for the parameter names. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- .../src/Repository/AlbumArtistRepository.cs | 12 ++--- .../src/Repository/AlbumRepository.cs | 8 +-- WaveBox.Core/src/Repository/ArtRepository.cs | 4 +- .../src/Repository/ArtistRepository.cs | 8 +-- .../src/Repository/FavoriteRepository.cs | 52 ++++++------------- .../src/Repository/FolderRepository.cs | 16 ++---- .../src/Repository/GenreRepository.cs | 4 +- WaveBox.Core/src/Repository/ItemRepository.cs | 4 +- .../src/Repository/MediaItemRepository.cs | 12 ++--- .../src/Repository/PlaylistRepository.cs | 4 +- .../src/Repository/SessionRepository.cs | 4 +- WaveBox.Core/src/Repository/SongRepository.cs | 4 +- WaveBox.Core/src/Repository/StatRepository.cs | 4 +- WaveBox.Core/src/Repository/UserRepository.cs | 8 +-- .../src/Repository/VideoRepository.cs | 4 +- 15 files changed, 41 insertions(+), 107 deletions(-) diff --git a/WaveBox.Core/src/Repository/AlbumArtistRepository.cs b/WaveBox.Core/src/Repository/AlbumArtistRepository.cs index 2c17066..07c2797 100644 --- a/WaveBox.Core/src/Repository/AlbumArtistRepository.cs +++ b/WaveBox.Core/src/Repository/AlbumArtistRepository.cs @@ -14,15 +14,9 @@ public class AlbumArtistRepository : IAlbumArtistRepository { private readonly ISongRepository songRepository; public AlbumArtistRepository(IDatabase database, IItemRepository itemRepository, ISongRepository songRepository) { - if (database == null) { - throw new ArgumentNullException("database"); - } - if (itemRepository == null) { - throw new ArgumentNullException("itemRepository"); - } - if (songRepository == null) { - throw new ArgumentNullException("songRepository"); - } + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(itemRepository); + ArgumentNullException.ThrowIfNull(songRepository); this.database = database; this.itemRepository = itemRepository; diff --git a/WaveBox.Core/src/Repository/AlbumRepository.cs b/WaveBox.Core/src/Repository/AlbumRepository.cs index 9b84985..f3b3e11 100644 --- a/WaveBox.Core/src/Repository/AlbumRepository.cs +++ b/WaveBox.Core/src/Repository/AlbumRepository.cs @@ -14,12 +14,8 @@ public class AlbumRepository : IAlbumRepository { private readonly IItemRepository itemRepository; public AlbumRepository(IDatabase database, IItemRepository itemRepository) { - if (database == null) { - throw new ArgumentNullException("database"); - } - if (itemRepository == null) { - throw new ArgumentNullException("itemRepository"); - } + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(itemRepository); this.database = database; this.itemRepository = itemRepository; diff --git a/WaveBox.Core/src/Repository/ArtRepository.cs b/WaveBox.Core/src/Repository/ArtRepository.cs index 9cbb8bc..a94356e 100644 --- a/WaveBox.Core/src/Repository/ArtRepository.cs +++ b/WaveBox.Core/src/Repository/ArtRepository.cs @@ -9,9 +9,7 @@ public class ArtRepository : IArtRepository { private readonly IDatabase database; public ArtRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } diff --git a/WaveBox.Core/src/Repository/ArtistRepository.cs b/WaveBox.Core/src/Repository/ArtistRepository.cs index 1481986..d4efe45 100644 --- a/WaveBox.Core/src/Repository/ArtistRepository.cs +++ b/WaveBox.Core/src/Repository/ArtistRepository.cs @@ -13,12 +13,8 @@ public class ArtistRepository : IArtistRepository { private readonly IItemRepository itemRepository; public ArtistRepository(IDatabase database, IItemRepository itemRepository) { - if (database == null) { - throw new ArgumentNullException("database"); - } - if (itemRepository == null) { - throw new ArgumentNullException("itemRepository"); - } + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(itemRepository); this.database = database; this.itemRepository = itemRepository; diff --git a/WaveBox.Core/src/Repository/FavoriteRepository.cs b/WaveBox.Core/src/Repository/FavoriteRepository.cs index ad74512..f2560ba 100644 --- a/WaveBox.Core/src/Repository/FavoriteRepository.cs +++ b/WaveBox.Core/src/Repository/FavoriteRepository.cs @@ -19,36 +19,16 @@ public class FavoriteRepository : IFavoriteRepository { private readonly IItemRepository itemRepository; public FavoriteRepository(IDatabase database, IAlbumArtistRepository albumArtistRepository, IAlbumRepository albumRepository, IArtistRepository artistRepository, IFolderRepository folderRepository, IGenreRepository genreRepository, IPlaylistRepository playlistRepository, ISongRepository songRepository, IVideoRepository videoRepository, IItemRepository itemRepository) { - if (database == null) { - throw new ArgumentNullException("database"); - } - if (albumRepository == null) { - throw new ArgumentNullException("albumRepository"); - } - if (albumArtistRepository == null) { - throw new ArgumentNullException("albumArtistRepository"); - } - if (artistRepository == null) { - throw new ArgumentNullException("artistRepository"); - } - if (folderRepository == null) { - throw new ArgumentNullException("folderRepository"); - } - if (genreRepository == null) { - throw new ArgumentNullException("genreRepository"); - } - if (playlistRepository == null) { - throw new ArgumentNullException("playlistRepository"); - } - if (songRepository == null) { - throw new ArgumentNullException("songRepository"); - } - if (videoRepository == null) { - throw new ArgumentNullException("videoRepository"); - } - if (itemRepository == null) { - throw new ArgumentNullException("itemRepository"); - } + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(albumRepository); + ArgumentNullException.ThrowIfNull(albumArtistRepository); + ArgumentNullException.ThrowIfNull(artistRepository); + ArgumentNullException.ThrowIfNull(folderRepository); + ArgumentNullException.ThrowIfNull(genreRepository); + ArgumentNullException.ThrowIfNull(playlistRepository); + ArgumentNullException.ThrowIfNull(songRepository); + ArgumentNullException.ThrowIfNull(videoRepository); + ArgumentNullException.ThrowIfNull(itemRepository); this.database = database; this.albumArtistRepository = albumArtistRepository; @@ -94,9 +74,10 @@ public IList FavoritesForUserId(int userId) { public IList FavoritesForArtistId(int? artistId, int? userId) { if (artistId == null) { - throw new ArgumentNullException("artistId"); - } else if (userId == null) { - throw new ArgumentNullException("userId"); + throw new ArgumentNullException(nameof(artistId)); + } + if (userId == null) { + throw new ArgumentNullException(nameof(userId)); } return this.database.GetList("SELECT * FROM Favorite LEFT JOIN Song ON Song.ItemId = Favorite.FavoriteItemId WHERE Song.ArtistId = ? AND Favorite.FavoriteUserId = ?", artistId, userId); @@ -105,8 +86,9 @@ public IList FavoritesForArtistId(int? artistId, int? userId) { public IList FavoritesForAlbumArtistId(int? albumArtistId, int? userId) { if (albumArtistId == null) { throw new ArgumentNullException(nameof(albumArtistId)); - } else if (userId == null) { - throw new ArgumentNullException("userId"); + } + if (userId == null) { + throw new ArgumentNullException(nameof(userId)); } return this.database.GetList("SELECT * FROM Favorite LEFT JOIN Song ON Song.ItemId = Favorite.FavoriteItemId WHERE Song.AlbumArtistId = ? AND Favorite.FavoriteUserId = ?", albumArtistId, userId); diff --git a/WaveBox.Core/src/Repository/FolderRepository.cs b/WaveBox.Core/src/Repository/FolderRepository.cs index d43629d..82b0e9a 100644 --- a/WaveBox.Core/src/Repository/FolderRepository.cs +++ b/WaveBox.Core/src/Repository/FolderRepository.cs @@ -15,18 +15,10 @@ public class FolderRepository : IFolderRepository { private readonly IVideoRepository videoRepository; public FolderRepository(IDatabase database, IServerSettings serverSettings, ISongRepository songRepository, IVideoRepository videoRepository) { - if (database == null) { - throw new ArgumentNullException("database"); - } - if (serverSettings == null) { - throw new ArgumentNullException("serverSettings"); - } - if (songRepository == null) { - throw new ArgumentNullException("songRepository"); - } - if (videoRepository == null) { - throw new ArgumentNullException("videoRepository"); - } + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(serverSettings); + ArgumentNullException.ThrowIfNull(songRepository); + ArgumentNullException.ThrowIfNull(videoRepository); this.database = database; this.serverSettings = serverSettings; diff --git a/WaveBox.Core/src/Repository/GenreRepository.cs b/WaveBox.Core/src/Repository/GenreRepository.cs index 407c305..a73cb2b 100644 --- a/WaveBox.Core/src/Repository/GenreRepository.cs +++ b/WaveBox.Core/src/Repository/GenreRepository.cs @@ -12,9 +12,7 @@ public class GenreRepository : IGenreRepository { private readonly IDatabase database; public GenreRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } diff --git a/WaveBox.Core/src/Repository/ItemRepository.cs b/WaveBox.Core/src/Repository/ItemRepository.cs index f14af8a..bcaad36 100644 --- a/WaveBox.Core/src/Repository/ItemRepository.cs +++ b/WaveBox.Core/src/Repository/ItemRepository.cs @@ -15,9 +15,7 @@ public class ItemRepository : IItemRepository { private readonly IDatabase database; public ItemRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } diff --git a/WaveBox.Core/src/Repository/MediaItemRepository.cs b/WaveBox.Core/src/Repository/MediaItemRepository.cs index 908f38f..08a9c77 100644 --- a/WaveBox.Core/src/Repository/MediaItemRepository.cs +++ b/WaveBox.Core/src/Repository/MediaItemRepository.cs @@ -7,15 +7,9 @@ public class MediaItemRepository : IMediaItemRepository { private readonly IVideoRepository videoRepository; public MediaItemRepository(IItemRepository itemRepository, ISongRepository songRepository, IVideoRepository videoRepository) { - if (itemRepository == null) { - throw new ArgumentNullException("itemRepository"); - } - if (songRepository == null) { - throw new ArgumentNullException("songRepository"); - } - if (videoRepository == null) { - throw new ArgumentNullException("videoRepository"); - } + ArgumentNullException.ThrowIfNull(itemRepository); + ArgumentNullException.ThrowIfNull(songRepository); + ArgumentNullException.ThrowIfNull(videoRepository); this.itemRepository = itemRepository; this.songRepository = songRepository; diff --git a/WaveBox.Core/src/Repository/PlaylistRepository.cs b/WaveBox.Core/src/Repository/PlaylistRepository.cs index d9c6106..a54a38c 100644 --- a/WaveBox.Core/src/Repository/PlaylistRepository.cs +++ b/WaveBox.Core/src/Repository/PlaylistRepository.cs @@ -10,9 +10,7 @@ public class PlaylistRepository : IPlaylistRepository { private readonly IDatabase database; public PlaylistRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } diff --git a/WaveBox.Core/src/Repository/SessionRepository.cs b/WaveBox.Core/src/Repository/SessionRepository.cs index bb3e706..f039e41 100644 --- a/WaveBox.Core/src/Repository/SessionRepository.cs +++ b/WaveBox.Core/src/Repository/SessionRepository.cs @@ -14,9 +14,7 @@ public class SessionRepository : ISessionRepository { private Dictionary Sessions { get; set; } public SessionRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; diff --git a/WaveBox.Core/src/Repository/SongRepository.cs b/WaveBox.Core/src/Repository/SongRepository.cs index fead699..e0352f1 100644 --- a/WaveBox.Core/src/Repository/SongRepository.cs +++ b/WaveBox.Core/src/Repository/SongRepository.cs @@ -13,9 +13,7 @@ public class SongRepository : ISongRepository { private readonly IDatabase database; public SongRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } diff --git a/WaveBox.Core/src/Repository/StatRepository.cs b/WaveBox.Core/src/Repository/StatRepository.cs index 5f0f5f4..956be46 100644 --- a/WaveBox.Core/src/Repository/StatRepository.cs +++ b/WaveBox.Core/src/Repository/StatRepository.cs @@ -9,9 +9,7 @@ public class StatRepository : IStatRepository { private readonly IDatabase database; public StatRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } diff --git a/WaveBox.Core/src/Repository/UserRepository.cs b/WaveBox.Core/src/Repository/UserRepository.cs index 6b66270..1cba38a 100644 --- a/WaveBox.Core/src/Repository/UserRepository.cs +++ b/WaveBox.Core/src/Repository/UserRepository.cs @@ -15,12 +15,8 @@ public class UserRepository : IUserRepository { private IDictionary Users { get; set; } public UserRepository(IDatabase database, IItemRepository itemRepository) { - if (database == null) { - throw new ArgumentNullException("database"); - } - if (itemRepository == null) { - throw new ArgumentNullException("itemRepository"); - } + ArgumentNullException.ThrowIfNull(database); + ArgumentNullException.ThrowIfNull(itemRepository); this.database = database; this.itemRepository = itemRepository; diff --git a/WaveBox.Core/src/Repository/VideoRepository.cs b/WaveBox.Core/src/Repository/VideoRepository.cs index 3947464..fbe03cb 100644 --- a/WaveBox.Core/src/Repository/VideoRepository.cs +++ b/WaveBox.Core/src/Repository/VideoRepository.cs @@ -11,9 +11,7 @@ public class VideoRepository : IVideoRepository { private readonly IDatabase database; public VideoRepository(IDatabase database) { - if (database == null) { - throw new ArgumentNullException("database"); - } + ArgumentNullException.ThrowIfNull(database); this.database = database; } From b163ed83cb720c55563e482173a5a732ff91cf2c Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:25:26 -0500 Subject: [PATCH 09/11] Fix analyzer warnings in vendored SQLiteNet.cs P/Invoke externs become internal (no callers outside the assembly) with BestFitMapping disabled on the string-marshaling imports; GetMapping calls with compile-time types use the generic overload; Bind* results are explicitly discarded; PreparedSqlLiteInsertCommand now declares IDisposable so its existing dispose pattern is recognized; plus small span/char-overload and Length-over-LINQ cleanups. The ArgumentException in DoSavePointExecute had its message passed as paramName; the arguments are now in the right order. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- WaveBox.Core/src/SQLiteNet.cs | 134 +++++++++++++++++----------------- 1 file changed, 69 insertions(+), 65 deletions(-) diff --git a/WaveBox.Core/src/SQLiteNet.cs b/WaveBox.Core/src/SQLiteNet.cs index b416805..1fca727 100644 --- a/WaveBox.Core/src/SQLiteNet.cs +++ b/WaveBox.Core/src/SQLiteNet.cs @@ -414,7 +414,7 @@ private struct IndexInfo { /// Executes a "drop table" on the database. This is non-recoverable. /// public int DropTable() { - var map = GetMapping(typeof (T), TableMappingType.Write); + var map = GetMapping(TableMappingType.Write); var query = string.Format("drop table if exists \"{0}\"", map.TableName); @@ -778,7 +778,7 @@ public IEnumerable DeferredQuery(ITableMapping map, string query, params /// if the object is not found. /// public T Get(object pk) where T : new() { - var map = GetMapping(typeof (T), TableMappingType.Read); + var map = GetMapping(TableMappingType.Read); return Query(map.GetByPrimaryKeySql, pk).First(); } @@ -810,7 +810,7 @@ public IEnumerable DeferredQuery(ITableMapping map, string query, params /// if the object is not found. /// public T Find(object pk) where T : new() { - var map = GetMapping(typeof (T), TableMappingType.Read); + var map = GetMapping(TableMappingType.Read); return Query(map.GetByPrimaryKeySql, pk).FirstOrDefault(); } @@ -996,7 +996,7 @@ private void DoSavePointExecute(string savepoint, string cmd) { int firstLen = savepoint.IndexOf('D'); if (firstLen >= 2 && savepoint.Length > firstLen + 1) { int depth; - if (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth)) { + if (Int32.TryParse(savepoint.AsSpan(firstLen + 1), out depth)) { // TODO: Mild race here, but inescapable without locking almost everywhere. if (0 <= depth && depth < _trasactionDepth) { Volatile.Write(ref _trasactionDepth, depth); @@ -1006,8 +1006,8 @@ private void DoSavePointExecute(string savepoint, string cmd) { } } - throw new ArgumentException("savePoint", - "savePoint is not valid, and should be the result of a call to SaveTransactionPoint."); + throw new ArgumentException("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", + nameof(savepoint)); } /// @@ -1208,7 +1208,7 @@ public virtual int Delete(object objectToDelete) { /// The type of object. /// public int Delete(object primaryKey) { - var map = GetMapping(typeof (T), TableMappingType.Write); + var map = GetMapping(TableMappingType.Write); var pk = map.PK; if (pk == null) { throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); @@ -1229,7 +1229,7 @@ public int Delete(object primaryKey) { /// The type of objects to delete. /// public int DeleteAll() { - var map = GetMapping(typeof (T), TableMappingType.Write); + var map = GetMapping(TableMappingType.Write); var query = string.Format("delete from \"{0}\"", map.TableName); return Execute(query); } @@ -1405,7 +1405,7 @@ public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, stri private PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra, InsertType insertType) { var cols = InsertColumns; string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) { + if (cols.Length == 0 && Columns.Length == 1 && Columns[0].IsAutoInc) { insertSql = string.Format(insertType.QueryText() + " {1} INTO \"{0}\" DEFAULT VALUES", TableName, extra); } else { insertSql = string.Format(insertType.QueryText() + " {3} INTO \"{0}\"({1}) VALUES ({2})", TableName, @@ -1614,11 +1614,11 @@ public int ExecuteNonQuery() { } public IEnumerable ExecuteDeferredQuery() { - return ExecuteDeferredQuery(_conn.GetMapping(typeof (T), TableMappingType.Read)); + return ExecuteDeferredQuery(_conn.GetMapping(TableMappingType.Read)); } public List ExecuteQuery() { - return ExecuteDeferredQuery(_conn.GetMapping(typeof (T), TableMappingType.Read)).ToList(); + return ExecuteDeferredQuery(_conn.GetMapping(TableMappingType.Read)).ToList(); } public List ExecuteQuery(ITableMapping map) { @@ -1741,35 +1741,35 @@ private void BindAll(Sqlite3Statement stmt) { internal static void BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) { if (value == null) { - SQLite3.BindNull(stmt, index); + _ = SQLite3.BindNull(stmt, index); } else { if (value is Int32) { - SQLite3.BindInt(stmt, index, (int) value); + _ = SQLite3.BindInt(stmt, index, (int) value); } else if (value is String) { - SQLite3.BindText(stmt, index, (string) value, -1, NegativePointer); + _ = SQLite3.BindText(stmt, index, (string) value, -1, NegativePointer); } else if (value is Byte || value is UInt16 || value is SByte || value is Int16) { - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); + _ = SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); } else if (value is Boolean) { - SQLite3.BindInt(stmt, index, (bool) value ? 1 : 0); + _ = SQLite3.BindInt(stmt, index, (bool) value ? 1 : 0); } else if (value is UInt32 || value is Int64) { - SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); + _ = SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); } else if (value is Single || value is Double || value is Decimal) { - SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); + _ = SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); } else if (value is DateTime) { if (storeDateTimeAsTicks) { - SQLite3.BindInt64(stmt, index, ((DateTime) value).Ticks); + _ = SQLite3.BindInt64(stmt, index, ((DateTime) value).Ticks); } else { - SQLite3.BindText(stmt, index, ((DateTime) value).ToString("yyyy-MM-dd HH:mm:ss"), -1, - NegativePointer); + _ = SQLite3.BindText(stmt, index, ((DateTime) value).ToString("yyyy-MM-dd HH:mm:ss"), -1, + NegativePointer); } #if !NETFX_CORE } else if (value.GetType().IsEnum) { #else } else if (value.GetType().GetTypeInfo().IsEnum) { #endif - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); + _ = SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); } else if (value is byte[]) { - SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); + _ = SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); #if SQLITE_SUPPORT_GUID } else if (value is Guid) { SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); @@ -1846,7 +1846,7 @@ private object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, T /// /// Since the insert never changed, we only need to prepare once. /// - public class PreparedSqlLiteInsertCommand : ISQLiteCommand { + public class PreparedSqlLiteInsertCommand : ISQLiteCommand, IDisposable { public bool Initialized { get; set; } protected SQLiteConnection Connection { get; set; } @@ -1957,7 +1957,7 @@ private TableQuery(SQLiteConnection conn, ITableMapping table) { public TableQuery(SQLiteConnection conn) { Connection = conn; - Table = Connection.GetMapping(typeof (T), TableMappingType.Read); + Table = Connection.GetMapping(TableMappingType.Read); } public TableQuery Clone() @@ -2060,7 +2060,7 @@ public TableQuery Join( Expression> resultSelector) where TInner : new() where TResult : new() { - var q = new TableQuery(Connection, Connection.GetMapping(typeof (TResult), TableMappingType.Read)) { + var q = new TableQuery(Connection, Connection.GetMapping(TableMappingType.Read)) { _joinOuter = this, _joinOuterKeySelector = outerKeySelector, _joinInner = inner, @@ -2235,15 +2235,15 @@ private CompileResult CompileExpr(Expression expr, List queryArgs) { // if (val != null && val is System.Collections.IEnumerable && !(val is string)) { var sb = new System.Text.StringBuilder(); - sb.Append("("); + sb.Append('('); var head = ""; foreach (var a in (System.Collections.IEnumerable) val) { queryArgs.Add(a); sb.Append(head); - sb.Append("?"); + sb.Append('?'); head = ","; } - sb.Append(")"); + sb.Append(')'); return new CompileResult { CommandText = sb.ToString(), Value = val @@ -2368,33 +2368,36 @@ public enum ConfigOption { } #if !WINDOWS_PHONE - [DllImport("e_sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); + [DllImport("e_sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl, + BestFitMapping = false)] + internal static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - [DllImport("e_sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, + [DllImport("e_sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl, + BestFitMapping = false)] + internal static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); [DllImport("e_sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); + internal static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); [DllImport("e_sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); + internal static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); [DllImport("e_sqlite3", EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Close(IntPtr db); + internal static extern Result Close(IntPtr db); [DllImport("e_sqlite3", EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Config(ConfigOption option); + internal static extern Result Config(ConfigOption option); [DllImport("e_sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] - public static extern Result BusyTimeout(IntPtr db, int milliseconds); + internal static extern Result BusyTimeout(IntPtr db, int milliseconds); [DllImport("e_sqlite3", EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] - public static extern int Changes(IntPtr db); + internal static extern int Changes(IntPtr db); - [DllImport("e_sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Prepare2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, + [DllImport("e_sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl, + BestFitMapping = false)] + internal static extern Result Prepare2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); public static IntPtr Prepare2(IntPtr db, string query) { @@ -2410,7 +2413,7 @@ public static IntPtr Prepare2(IntPtr db, string query) { // first statement of a script. This one hands back the pointer to the remaining SQL, // which is what lets ExecuteScript walk a multi-statement file. [DllImport("e_sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Prepare2(IntPtr db, IntPtr sql, int numBytes, + internal static extern Result Prepare2(IntPtr db, IntPtr sql, int numBytes, out IntPtr stmt, out IntPtr pzTail); /// @@ -2488,52 +2491,53 @@ private static string RemainingText(byte[] utf8, int offset) { } [DllImport("e_sqlite3", EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Step(IntPtr stmt); + internal static extern Result Step(IntPtr stmt); [DllImport("e_sqlite3", EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Reset(IntPtr stmt); + internal static extern Result Reset(IntPtr stmt); [DllImport("e_sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Finalize(IntPtr stmt); + internal static extern Result Finalize(IntPtr stmt); [DllImport("e_sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] - public static extern long LastInsertRowid(IntPtr db); + internal static extern long LastInsertRowid(IntPtr db); [DllImport("e_sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr Errmsg(IntPtr db); + internal static extern IntPtr Errmsg(IntPtr db); public static string GetErrmsg(IntPtr db) { return Marshal.PtrToStringUni(Errmsg(db)); } - [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); + [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl, + BestFitMapping = false)] + internal static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindNull(IntPtr stmt, int index); + internal static extern int BindNull(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt(IntPtr stmt, int index, int val); + internal static extern int BindInt(IntPtr stmt, int index, int val); [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt64(IntPtr stmt, int index, long val); + internal static extern int BindInt64(IntPtr stmt, int index, long val); [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindDouble(IntPtr stmt, int index, double val); + internal static extern int BindDouble(IntPtr stmt, int index, double val); [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText(IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, + internal static extern int BindText(IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); [DllImport("e_sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free); + internal static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnCount(IntPtr stmt); + internal static extern int ColumnCount(IntPtr stmt); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnName(IntPtr stmt, int index); + internal static extern IntPtr ColumnName(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr ColumnName16Internal(IntPtr stmt, int index); @@ -2543,28 +2547,28 @@ public static string ColumnName16(IntPtr stmt, int index) { } [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] - public static extern ColType ColumnType(IntPtr stmt, int index); + internal static extern ColType ColumnType(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnInt(IntPtr stmt, int index); + internal static extern int ColumnInt(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern long ColumnInt64(IntPtr stmt, int index); + internal static extern long ColumnInt64(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] - public static extern double ColumnDouble(IntPtr stmt, int index); + internal static extern double ColumnDouble(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText(IntPtr stmt, int index); + internal static extern IntPtr ColumnText(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16(IntPtr stmt, int index); + internal static extern IntPtr ColumnText16(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob(IntPtr stmt, int index); + internal static extern IntPtr ColumnBlob(IntPtr stmt, int index); [DllImport("e_sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnBytes(IntPtr stmt, int index); + internal static extern int ColumnBytes(IntPtr stmt, int index); public static string ColumnString(IntPtr stmt, int index) { return Marshal.PtrToStringUni(SQLite3.ColumnText16(stmt, index)); From f6560a5149c38a3238ea613a5e6255fc8c6901cc Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:27:10 -0500 Subject: [PATCH 10/11] Fix test-suite analyzer warnings Test-class Dispose() methods call GC.SuppressFinalize (CA1816), E2E HTTP calls pass TestContext.Current.CancellationToken so test cancellation stays responsive (xUnit1051), and the starred-songs count assertion uses Assert.Single (xUnit2013). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- tests/WaveBox.E2E.Tests/LegacyApiTests.cs | 2 +- tests/WaveBox.E2E.Tests/SubsonicAnnotationTests.cs | 2 +- tests/WaveBox.E2E.Tests/SubsonicStreamTests.cs | 4 ++-- tests/WaveBox.E2E.Tests/SubsonicSystemTests.cs | 2 +- tests/WaveBox.Server.Tests/ApiAuthenticateTests.cs | 1 + tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs | 1 + tests/WaveBox.Server.Tests/DatabaseSetupTests.cs | 1 + tests/WaveBox.Server.Tests/FolderScanTests.cs | 1 + tests/WaveBox.Server.Tests/PlaylistTests.cs | 1 + tests/WaveBox.Server.Tests/RepositorySmokeTests.cs | 1 + tests/WaveBox.Server.Tests/ServerSettingsTests.cs | 1 + tests/WaveBox.Server.Tests/SessionRepositoryTests.cs | 1 + tests/WaveBox.Server.Tests/SqliteScriptTests.cs | 1 + tests/WaveBox.Server.Tests/SubsonicAuthTests.cs | 1 + tests/WaveBox.Server.Tests/UserRepositoryTests.cs | 1 + 15 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/WaveBox.E2E.Tests/LegacyApiTests.cs b/tests/WaveBox.E2E.Tests/LegacyApiTests.cs index 9fde07f..82d15af 100644 --- a/tests/WaveBox.E2E.Tests/LegacyApiTests.cs +++ b/tests/WaveBox.E2E.Tests/LegacyApiTests.cs @@ -45,7 +45,7 @@ public async Task ScannedSongHasParsedTagMetadata() { public async Task StreamRangeRequestReturns206() { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "api/stream/" + server.SongId + "?s=" + server.Session); request.Headers.Range = new RangeHeaderValue(100, 199); - using (HttpResponseMessage response = await server.Client.SendAsync(request)) { + using (HttpResponseMessage response = await server.Client.SendAsync(request, TestContext.Current.CancellationToken)) { Assert.Equal(HttpStatusCode.PartialContent, response.StatusCode); } } diff --git a/tests/WaveBox.E2E.Tests/SubsonicAnnotationTests.cs b/tests/WaveBox.E2E.Tests/SubsonicAnnotationTests.cs index 8e6300b..2288f81 100644 --- a/tests/WaveBox.E2E.Tests/SubsonicAnnotationTests.cs +++ b/tests/WaveBox.E2E.Tests/SubsonicAnnotationTests.cs @@ -18,7 +18,7 @@ public async Task StarGetStarredUnstarRoundTrip() { try { JsonNode starred = await SubsonicClient.Rest(server.Client, "getStarred2", SubsonicClient.Auth); JsonArray songs = (JsonArray)starred["starred2"]["song"]; - Assert.Equal(1, songs.Count); + Assert.Single(songs); Assert.NotNull(songs[0]["starred"]); } finally { await SubsonicClient.Rest(server.Client, "unstar", SubsonicClient.Auth + "&id=" + server.SongId); diff --git a/tests/WaveBox.E2E.Tests/SubsonicStreamTests.cs b/tests/WaveBox.E2E.Tests/SubsonicStreamTests.cs index 359bcdf..139318b 100644 --- a/tests/WaveBox.E2E.Tests/SubsonicStreamTests.cs +++ b/tests/WaveBox.E2E.Tests/SubsonicStreamTests.cs @@ -18,7 +18,7 @@ public async Task RawStreamRangeRequestReturns206() { HttpRequestMessage request = new HttpRequestMessage( HttpMethod.Get, "rest/stream?u=test&p=test&id=" + server.SongId + "&format=raw"); request.Headers.Range = new RangeHeaderValue(100, 199); - using (HttpResponseMessage response = await server.Client.SendAsync(request)) { + using (HttpResponseMessage response = await server.Client.SendAsync(request, TestContext.Current.CancellationToken)) { Assert.Equal(HttpStatusCode.PartialContent, response.StatusCode); } } @@ -28,7 +28,7 @@ public async Task TranscodedStreamReturnsAudio() { Assert.SkipUnless(WaveBoxServerFixture.FfmpegPresent, "ffmpeg is not installed"); byte[] audio = await server.Client.GetByteArrayAsync( - "rest/stream?u=test&p=test&id=" + server.SongId + "&maxBitRate=32&format=mp3"); + "rest/stream?u=test&p=test&id=" + server.SongId + "&maxBitRate=32&format=mp3", TestContext.Current.CancellationToken); Assert.True(audio.Length > 0, "expected transcoded audio bytes, got none"); } } diff --git a/tests/WaveBox.E2E.Tests/SubsonicSystemTests.cs b/tests/WaveBox.E2E.Tests/SubsonicSystemTests.cs index a1d71b0..3874ec1 100644 --- a/tests/WaveBox.E2E.Tests/SubsonicSystemTests.cs +++ b/tests/WaveBox.E2E.Tests/SubsonicSystemTests.cs @@ -14,7 +14,7 @@ public SubsonicSystemTests(WaveBoxServerFixture server) { [Fact] public async Task PingDefaultsToXmlEnvelope() { - string body = await server.Client.GetStringAsync("rest/ping.view?u=test&p=test"); + string body = await server.Client.GetStringAsync("rest/ping.view?u=test&p=test", TestContext.Current.CancellationToken); XDocument doc = XDocument.Parse(body); XNamespace ns = "http://subsonic.org/restapi"; Assert.Equal(ns + "subsonic-response", doc.Root.Name); diff --git a/tests/WaveBox.Server.Tests/ApiAuthenticateTests.cs b/tests/WaveBox.Server.Tests/ApiAuthenticateTests.cs index be5070f..38ba8b5 100644 --- a/tests/WaveBox.Server.Tests/ApiAuthenticateTests.cs +++ b/tests/WaveBox.Server.Tests/ApiAuthenticateTests.cs @@ -20,6 +20,7 @@ public ApiAuthenticateTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } [Fact] diff --git a/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs b/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs index edc072b..c8a2afa 100644 --- a/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs +++ b/tests/WaveBox.Server.Tests/DatabaseMigratorTests.cs @@ -36,6 +36,7 @@ public void Dispose() { Directory.Delete(workDir, true); } catch (IOException) { } + GC.SuppressFinalize(this); } private void WriteMigration(string name, string sql) { diff --git a/tests/WaveBox.Server.Tests/DatabaseSetupTests.cs b/tests/WaveBox.Server.Tests/DatabaseSetupTests.cs index b360ea9..f8ce290 100644 --- a/tests/WaveBox.Server.Tests/DatabaseSetupTests.cs +++ b/tests/WaveBox.Server.Tests/DatabaseSetupTests.cs @@ -17,6 +17,7 @@ public DatabaseSetupTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } private static int Scalar(string sql) { diff --git a/tests/WaveBox.Server.Tests/FolderScanTests.cs b/tests/WaveBox.Server.Tests/FolderScanTests.cs index 2a9f69b..3bf08bd 100644 --- a/tests/WaveBox.Server.Tests/FolderScanTests.cs +++ b/tests/WaveBox.Server.Tests/FolderScanTests.cs @@ -19,6 +19,7 @@ public FolderScanTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } [Fact] diff --git a/tests/WaveBox.Server.Tests/PlaylistTests.cs b/tests/WaveBox.Server.Tests/PlaylistTests.cs index e61b036..180852e 100644 --- a/tests/WaveBox.Server.Tests/PlaylistTests.cs +++ b/tests/WaveBox.Server.Tests/PlaylistTests.cs @@ -21,6 +21,7 @@ public PlaylistTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } private static Playlist Create(string name) { diff --git a/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs b/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs index 6710aef..1f00ee9 100644 --- a/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs +++ b/tests/WaveBox.Server.Tests/RepositorySmokeTests.cs @@ -22,6 +22,7 @@ public RepositorySmokeTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } [Fact] diff --git a/tests/WaveBox.Server.Tests/ServerSettingsTests.cs b/tests/WaveBox.Server.Tests/ServerSettingsTests.cs index e3f6f0a..b3d341a 100644 --- a/tests/WaveBox.Server.Tests/ServerSettingsTests.cs +++ b/tests/WaveBox.Server.Tests/ServerSettingsTests.cs @@ -19,6 +19,7 @@ public ServerSettingsTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } [Fact] diff --git a/tests/WaveBox.Server.Tests/SessionRepositoryTests.cs b/tests/WaveBox.Server.Tests/SessionRepositoryTests.cs index bc612c3..52f5b04 100644 --- a/tests/WaveBox.Server.Tests/SessionRepositoryTests.cs +++ b/tests/WaveBox.Server.Tests/SessionRepositoryTests.cs @@ -19,6 +19,7 @@ public SessionRepositoryTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } [Fact] diff --git a/tests/WaveBox.Server.Tests/SqliteScriptTests.cs b/tests/WaveBox.Server.Tests/SqliteScriptTests.cs index 6dcd234..6cfdb83 100644 --- a/tests/WaveBox.Server.Tests/SqliteScriptTests.cs +++ b/tests/WaveBox.Server.Tests/SqliteScriptTests.cs @@ -20,6 +20,7 @@ public SqliteScriptTests() { public void Dispose() { conn.Dispose(); File.Delete(dbPath); + GC.SuppressFinalize(this); } [Fact] diff --git a/tests/WaveBox.Server.Tests/SubsonicAuthTests.cs b/tests/WaveBox.Server.Tests/SubsonicAuthTests.cs index 377696e..53908e5 100644 --- a/tests/WaveBox.Server.Tests/SubsonicAuthTests.cs +++ b/tests/WaveBox.Server.Tests/SubsonicAuthTests.cs @@ -23,6 +23,7 @@ public SubsonicAuthTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } private static SubsonicRequest Request(string queryString) { diff --git a/tests/WaveBox.Server.Tests/UserRepositoryTests.cs b/tests/WaveBox.Server.Tests/UserRepositoryTests.cs index 8fc1535..822fe6b 100644 --- a/tests/WaveBox.Server.Tests/UserRepositoryTests.cs +++ b/tests/WaveBox.Server.Tests/UserRepositoryTests.cs @@ -19,6 +19,7 @@ public UserRepositoryTests() { public void Dispose() { harness.Dispose(); + GC.SuppressFinalize(this); } [Fact] From 2faf5cd0b46390b3eef25120a2f6dea6ecf1b061 Mon Sep 17 00:00:00 2001 From: Ben Baron Date: Sun, 26 Jul 2026 20:27:33 -0500 Subject: [PATCH 11/11] Treat warnings as errors now that the build is warning-free Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u4sFGNHRSAKp1Hjdr1oRF --- Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Directory.Build.props b/Directory.Build.props index 5f46ae2..3a817b2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,6 +5,7 @@ disable disable latest-minimum + true en