diff --git a/.editorconfig b/.editorconfig index 66c7c85..acf87df 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,21 @@ # S112: General exceptions should never be thrown dotnet_diagnostic.S112.severity = silent + +# U2U1202: Use LINQ Count methods efficiently +dotnet_diagnostic.U2U1202.severity = silent + +# RCS1037: Remove trailing white-space. +dotnet_diagnostic.RCS1037.severity = silent + +# U2U1108: StringBuilders should be initialized with capacity +dotnet_diagnostic.U2U1108.severity = silent + +# U2U1009: Async or iterator methods should avoid state machine generation for early exits (throws or synchronous returns) +dotnet_diagnostic.U2U1009.severity = silent + +# U2U1212: Capture intermediate results in lambda expressions +dotnet_diagnostic.U2U1212.severity = silent + +# U2U1201: Local collections should be initialized with capacity +dotnet_diagnostic.U2U1201.severity = silent diff --git a/JWLMerge.BackupFileServices/BackupFileService.cs b/JWLMerge.BackupFileServices/BackupFileService.cs index 1e0fcce..7171632 100644 --- a/JWLMerge.BackupFileServices/BackupFileService.cs +++ b/JWLMerge.BackupFileServices/BackupFileService.cs @@ -7,13 +7,13 @@ using System.Linq; using System.Security.Cryptography; using System.Text; - using JWLMerge.BackupFileServices.Events; - using JWLMerge.BackupFileServices.Exceptions; - using JWLMerge.BackupFileServices.Helpers; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.BackupFileServices.Models.ManifestFile; - using JWLMerge.ExcelServices; + using Events; + using Exceptions; + using Helpers; + using Models; + using Models.DatabaseModels; + using Models.ManifestFile; + using ExcelServices; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; @@ -33,7 +33,7 @@ _merger.ProgressEvent += MergerProgressEvent; } - public event EventHandler ProgressEvent; + public event EventHandler? ProgressEvent; /// public BackupFile Load(string backupFilePath) @@ -53,18 +53,16 @@ var filename = Path.GetFileName(backupFilePath); ProgressMessage($"Loading {filename}"); - using (var archive = new ZipArchive(File.OpenRead(backupFilePath), ZipArchiveMode.Read)) + using var archive = new ZipArchive(File.OpenRead(backupFilePath), ZipArchiveMode.Read); + var manifest = ReadManifest(filename, archive); + + var database = ReadDatabase(archive, manifest.UserDataBackup.DatabaseName); + + return new BackupFile { - var manifest = ReadManifest(filename, archive); - - var database = ReadDatabase(archive, manifest.UserDataBackup.DatabaseName); - - return new BackupFile - { - Manifest = manifest, - Database = database, - }; - } + Manifest = manifest, + Database = database, + }; } catch (UnauthorizedAccessException) { @@ -137,7 +135,7 @@ /// public int RemoveNotesByTag( BackupFile backup, - int[] tagIds, + int[]? tagIds, bool removeUntaggedNotes, bool removeAssociatedUnderlining, bool removeAssociatedTags) @@ -147,10 +145,7 @@ throw new ArgumentNullException(nameof(backup)); } - if (tagIds == null) - { - tagIds = Array.Empty(); - } + tagIds ??= Array.Empty(); var tagIdsHash = tagIds.ToHashSet(); @@ -199,17 +194,14 @@ } /// - public int RemoveUnderliningByColour(BackupFile backup, int[] colorIndexes, bool removeAssociatedNotes) + public int RemoveUnderliningByColour(BackupFile backup, int[]? colorIndexes, bool removeAssociatedNotes) { if (backup == null) { throw new ArgumentNullException(nameof(backup)); } - if (colorIndexes == null) - { - colorIndexes = Array.Empty(); - } + colorIndexes ??= Array.Empty(); var userMarkIdsToRemove = new HashSet(); @@ -229,7 +221,7 @@ BackupFile backup, int colorIndex, bool anyColor, - string publicationSymbol, + string? publicationSymbol, bool anyPublication, bool removeAssociatedNotes) { @@ -277,48 +269,45 @@ } ProgressMessage("Writing merged database file"); - - using (var memoryStream = new MemoryStream()) + + using var memoryStream = new MemoryStream(); + using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) { - using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) + Log.Logger.Debug("Created ZipArchive"); + + var tmpDatabaseFileName = ExtractDatabaseToFile(originalJwlibraryFilePathForSchema); + try { - Log.Logger.Debug("Created ZipArchive"); + backup.Manifest.UserDataBackup.Hash = GenerateDatabaseHash(tmpDatabaseFileName); - var tmpDatabaseFileName = ExtractDatabaseToFile(originalJwlibraryFilePathForSchema); - try + var manifestEntry = archive.CreateEntry(ManifestEntryName); + using (var entryStream = manifestEntry.Open()) + using (var streamWriter = new StreamWriter(entryStream)) { - backup.Manifest.UserDataBackup.Hash = GenerateDatabaseHash(tmpDatabaseFileName); - - var manifestEntry = archive.CreateEntry(ManifestEntryName); - using (var entryStream = manifestEntry.Open()) - using (var streamWriter = new StreamWriter(entryStream)) - { - streamWriter.Write( - JsonConvert.SerializeObject( - backup.Manifest, - new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - })); - } + streamWriter.Write( + JsonConvert.SerializeObject( + backup.Manifest, + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + })); + } - AddDatabaseEntryToArchive(archive, backup.Database, tmpDatabaseFileName); - } - finally - { - Log.Logger.Debug("Deleting {tmpDatabaseFileName}", tmpDatabaseFileName); - File.Delete(tmpDatabaseFileName); - } + AddDatabaseEntryToArchive(archive, backup.Database, tmpDatabaseFileName); } - - using (var fileStream = new FileStream(newDatabaseFilePath, FileMode.Create)) + finally { - ProgressMessage("Finishing"); - - memoryStream.Seek(0, SeekOrigin.Begin); - memoryStream.CopyTo(fileStream); + Log.Logger.Debug("Deleting {tmpDatabaseFileName}", tmpDatabaseFileName); + File.Delete(tmpDatabaseFileName); } } + + using var fileStream = new FileStream(newDatabaseFilePath, FileMode.Create); + + ProgressMessage("Finishing"); + + memoryStream.Seek(0, SeekOrigin.Begin); + memoryStream.CopyTo(fileStream); } /// @@ -407,7 +396,7 @@ } } - int countRemoved = 0; + var countRemoved = 0; foreach (var userMark in Enumerable.Reverse(database.UserMarks)) { if (!userMarksToRetain.Contains(userMark.UserMarkId)) @@ -457,7 +446,7 @@ ProgressMessage($"Merging {files.Count} backup files"); - int fileNumber = 1; + var fileNumber = 1; var originals = new List(); foreach (var file in files) { @@ -547,10 +536,9 @@ } } - notesToWrite.Add(new ExcelServices.Models.BibleNote + notesToWrite.Add(new ExcelServices.Models.BibleNote( + location.BookNumber.Value, BibleBookNames.GetName(location.BookNumber.Value)) { - BookNumber = location.BookNumber.Value, - BookName = BibleBookNames.GetName(location.BookNumber.Value), ChapterNumber = location.ChapterNumber, VerseNumber = note.BlockIdentifier, NoteTitle = note.Title?.Trim(), @@ -573,13 +561,14 @@ private static string GetTagsAsCsv(ILookup tags, int noteId, Database database) { - var t = tags[noteId]?.ToArray(); - if (t == null || !t.Any()) + var t = tags[noteId].ToArray(); + if (!t.Any()) { return string.Empty; } - return string.Join(", ", t.Select(x => database.FindTag(x.TagId).Name)); + var tagNames = t.Select(x => database.FindTag(x.TagId)?.Name).Where(x => !string.IsNullOrEmpty(x)); + return string.Join(", ", tagNames); } private static bool SupportDatabaseVersion(int version) @@ -641,7 +630,7 @@ } private static bool ShouldRemoveUnderlining( - UserMark mark, Database database, int colorIndex, bool anyColor, string publicationSymbol, bool anyPublication) + UserMark mark, Database database, int colorIndex, bool anyColor, string? publicationSymbol, bool anyPublication) { if (!anyColor && mark.ColorIndex != colorIndex) { @@ -654,7 +643,7 @@ } var location = database.FindLocation(mark.LocationId); - return location.KeySymbol == publicationSymbol; + return location != null && location.KeySymbol == publicationSymbol; } private static int RemoveUnderlining(Database database, HashSet userMarkIdsToRemove, bool removeAssociatedNotes) @@ -797,20 +786,18 @@ private string ExtractDatabaseToFile(string jwlibraryFile) { Log.Logger.Debug("Opening ZipArchive {jwlibraryFile}", jwlibraryFile); - - using (var archive = new ZipArchive(File.OpenRead(jwlibraryFile), ZipArchiveMode.Read)) - { - var manifest = ReadManifest(Path.GetFileName(jwlibraryFile), archive); - var databaseEntry = archive.Entries.FirstOrDefault(x => x.Name.Equals(manifest.UserDataBackup.DatabaseName, StringComparison.OrdinalIgnoreCase)); + using var archive = new ZipArchive(File.OpenRead(jwlibraryFile), ZipArchiveMode.Read); + var manifest = ReadManifest(Path.GetFileName(jwlibraryFile), archive); + + var databaseEntry = archive.Entries.FirstOrDefault(x => x.Name.Equals(manifest.UserDataBackup.DatabaseName, StringComparison.OrdinalIgnoreCase)); #pragma warning disable S5445 // Insecure temporary file creation methods should not be used - var tmpFile = Path.GetTempFileName(); + var tmpFile = Path.GetTempFileName(); #pragma warning restore S5445 // Insecure temporary file creation methods should not be used - databaseEntry.ExtractToFile(tmpFile, overwrite: true); + databaseEntry.ExtractToFile(tmpFile, overwrite: true); - Log.Logger.Information("Created temp file: {tmpDatabaseFileName}", tmpFile); - return tmpFile; - } + Log.Logger.Information("Created temp file: {tmpDatabaseFileName}", tmpFile); + return tmpFile; } private Manifest ReadManifest(string filename, ZipArchive archive) @@ -822,32 +809,81 @@ { throw new BackupFileServicesException($"Could not find manifest entry in jwlibrary file: {filename}"); } - - using (var stream = new StreamReader(manifestEntry.Open())) - { - var fileContents = stream.ReadToEnd(); - Log.Logger.Debug("Parsing manifest"); - dynamic data = JObject.Parse(fileContents); + using var stream = new StreamReader(manifestEntry.Open()); + + var fileContents = stream.ReadToEnd(); + + Log.Logger.Debug("Parsing manifest"); + dynamic data = JObject.Parse(fileContents); - int manifestVersion = data.version ?? 0; - if (!SupportManifestVersion(manifestVersion)) - { - throw new WrongManifestVersionException(filename, ManifestVersionSupported, manifestVersion); - } + int manifestVersion = data.version ?? 0; + if (!SupportManifestVersion(manifestVersion)) + { + throw new WrongManifestVersionException(filename, ManifestVersionSupported, manifestVersion); + } - int databaseVersion = data.userDataBackup.schemaVersion ?? 0; - if (!SupportDatabaseVersion(databaseVersion)) - { - throw new WrongDatabaseVersionException(filename, DatabaseVersionSupported, databaseVersion); - } + int databaseVersion = data.userDataBackup?.schemaVersion ?? 0; + if (!SupportDatabaseVersion(databaseVersion)) + { + throw new WrongDatabaseVersionException(filename, DatabaseVersionSupported, databaseVersion); + } - var result = JsonConvert.DeserializeObject(fileContents); + var result = JsonConvert.DeserializeObject(fileContents); - var prettyJson = JsonConvert.SerializeObject(result, Formatting.Indented); - Log.Logger.Debug("Parsed manifest {manifestJson}", prettyJson); + ValidateManifest(filename, result); - return result; + var prettyJson = JsonConvert.SerializeObject(result, Formatting.Indented); + Log.Logger.Debug("Parsed manifest {manifestJson}", prettyJson); + + return result!; + } + + private static void ValidateManifest(string filename, Manifest? result) + { + if (result == null) + { + throw new BackupFileServicesException($"Could not deserialize manifest entry from jwlibrary file: {filename}"); + } + + if (string.IsNullOrEmpty(result.Name)) + { + throw new BackupFileServicesException($"Could not retrieve manifest name from jwlibrary file: {filename}"); + } + + if (string.IsNullOrEmpty(result.CreationDate)) + { + throw new BackupFileServicesException($"Could not retrieve manifest creation date from jwlibrary file: {filename}"); + } + + ValidateUserDataBackup(filename, result.UserDataBackup); + } + + private static void ValidateUserDataBackup(string filename, UserDataBackup userDataBackup) + { + if (userDataBackup == null) + { + throw new BackupFileServicesException($"Could not retrieve UserDataBackup element from jwlibrary file: {filename}"); + } + + if (string.IsNullOrEmpty(userDataBackup.DatabaseName)) + { + throw new BackupFileServicesException($"DatabaseName element empty in UserDataBackup from jwlibrary file: {filename}"); + } + + if (string.IsNullOrEmpty(userDataBackup.DeviceName)) + { + throw new BackupFileServicesException($"DeviceName element empty in UserDataBackup from jwlibrary file: {filename}"); + } + + if (string.IsNullOrEmpty(userDataBackup.Hash)) + { + throw new BackupFileServicesException($"Hash element empty in UserDataBackup from jwlibrary file: {filename}"); + } + + if (string.IsNullOrEmpty(userDataBackup.LastModifiedDate)) + { + throw new BackupFileServicesException($"LastModifiedDate element empty in UserDataBackup from jwlibrary file: {filename}"); } } @@ -862,21 +898,18 @@ { ProgressMessage("Generating database hash"); - using (var fs = new FileStream(databaseFilePath, FileMode.Open)) - { - using (var bs = new BufferedStream(fs)) - using (var sha1 = new SHA256Managed()) - { - byte[] hash = sha1.ComputeHash(bs); - var sb = new StringBuilder(2 * hash.Length); - foreach (byte b in hash) - { - sb.Append($"{b:x2}"); - } + using var fs = new FileStream(databaseFilePath, FileMode.Open); + using var bs = new BufferedStream(fs); + using var sha1 = new SHA256Managed(); - return sb.ToString(); - } + byte[] hash = sha1.ComputeHash(bs); + var sb = new StringBuilder(2 * hash.Length); + foreach (byte b in hash) + { + sb.Append($"{b:x2}"); } + + return sb.ToString(); } private void AddDatabaseEntryToArchive( @@ -904,7 +937,7 @@ private void OnProgressEvent(string message) { - OnProgressEvent(new ProgressEventArgs { Message = message }); + OnProgressEvent(new ProgressEventArgs(message)); } private void ProgressMessage(string logMessage) diff --git a/JWLMerge.BackupFileServices/Events/ProgressEventArgs.cs b/JWLMerge.BackupFileServices/Events/ProgressEventArgs.cs index be57c52..bcc2e12 100644 --- a/JWLMerge.BackupFileServices/Events/ProgressEventArgs.cs +++ b/JWLMerge.BackupFileServices/Events/ProgressEventArgs.cs @@ -4,6 +4,11 @@ public class ProgressEventArgs : EventArgs { - public string Message { get; set; } + public ProgressEventArgs(string msg) + { + Message = msg; + } + + public string Message { get; } } } diff --git a/JWLMerge.BackupFileServices/Exceptions/WrongDatabaseVersionException.cs b/JWLMerge.BackupFileServices/Exceptions/WrongDatabaseVersionException.cs index 5733e83..bd4b01e 100644 --- a/JWLMerge.BackupFileServices/Exceptions/WrongDatabaseVersionException.cs +++ b/JWLMerge.BackupFileServices/Exceptions/WrongDatabaseVersionException.cs @@ -4,7 +4,9 @@ using System.Runtime.Serialization; [Serializable] +#pragma warning disable RCS1194 // Implement exception constructors. public class WrongDatabaseVersionException : BackupFileServicesException +#pragma warning restore RCS1194 // Implement exception constructors. { public WrongDatabaseVersionException(string filename, int expectedVersion, int foundVersion) : base($"Wrong database version found ({foundVersion}) in {filename}. Expecting {expectedVersion}") @@ -20,7 +22,7 @@ { } - public string Filename { get; } + public string? Filename { get; } public int ExpectedVersion { get; } diff --git a/JWLMerge.BackupFileServices/Exceptions/WrongManifestVersionException.cs b/JWLMerge.BackupFileServices/Exceptions/WrongManifestVersionException.cs index 025717d..82b88c1 100644 --- a/JWLMerge.BackupFileServices/Exceptions/WrongManifestVersionException.cs +++ b/JWLMerge.BackupFileServices/Exceptions/WrongManifestVersionException.cs @@ -4,7 +4,9 @@ using System.Runtime.Serialization; [Serializable] +#pragma warning disable RCS1194 // Implement exception constructors. public class WrongManifestVersionException : BackupFileServicesException +#pragma warning restore RCS1194 // Implement exception constructors. { public WrongManifestVersionException(string filename, int expectedVersion, int foundVersion) : base($"Wrong manifest version found ({foundVersion}) in {filename}. Expecting {expectedVersion}") @@ -20,7 +22,7 @@ { } - public string Filename { get; } + public string? Filename { get; } public int ExpectedVersion { get; } diff --git a/JWLMerge.BackupFileServices/Helpers/BibleNotesFile.cs b/JWLMerge.BackupFileServices/Helpers/BibleNotesFile.cs index fcfd11e..8f20ed7 100644 --- a/JWLMerge.BackupFileServices/Helpers/BibleNotesFile.cs +++ b/JWLMerge.BackupFileServices/Helpers/BibleNotesFile.cs @@ -4,18 +4,18 @@ using System.Collections.Generic; using System.IO; using System.Linq; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.DatabaseModels; + using Models; + using Models.DatabaseModels; public class BibleNotesFile { private const int MaxTitleLength = 50; - private const string BibleKeySymbolToken = @"[BibleKeySymbol"; - private const string MepsLanguageIdToken = @"[MepsLanguageId"; + private const string BibleKeySymbolToken = "[BibleKeySymbol"; + private const string MepsLanguageIdToken = "[MepsLanguageId"; private readonly List _notes = new List(); private readonly string _path; - private string _bibleKeySymbol; + private string _bibleKeySymbol = null!; private int _mepsLanguageId; private bool _initialised; @@ -61,15 +61,17 @@ ParseNotes(lines); } - private void RemoveParamLines(string[] lines) + private static void RemoveParamLines(string[] lines) { RemoveLineStarting(BibleKeySymbolToken, lines); RemoveLineStarting(MepsLanguageIdToken, lines); } - private void RemoveLineStarting(string token, string[] lines) + private static void RemoveLineStarting(string token, string[] lines) { - for (int n = 0; n < lines.Length; ++n) +#pragma warning disable U2U1015 // Do not index an array multiple times within a loop body + for (var n = 0; n < lines.Length; ++n) +#pragma warning restore U2U1015 // Do not index an array multiple times within a loop body { if (lines[n].Trim().StartsWith(token, StringComparison.OrdinalIgnoreCase)) { @@ -98,7 +100,7 @@ { var linesInNote = new List(); - BibleNotesVerseSpecification currentVerseSpec = null; + BibleNotesVerseSpecification? currentVerseSpec = null; foreach (var line in lines) { @@ -122,7 +124,7 @@ StoreNote(linesInNote, currentVerseSpec); } - private void StoreNote(IReadOnlyList lines, BibleNotesVerseSpecification currentVerseSpec) + private void StoreNote(IReadOnlyList lines, BibleNotesVerseSpecification? currentVerseSpec) { if (currentVerseSpec == null) { @@ -142,15 +144,15 @@ currentVerseSpec.ChapterNumber, currentVerseSpec.VerseNumber), - NoteTitle = titleAndContent.Title.Trim(), - NoteContent = titleAndContent.Content.Trim(), + NoteTitle = titleAndContent.Title?.Trim(), + NoteContent = titleAndContent.Content?.Trim(), ColourIndex = currentVerseSpec.ColourIndex, StartTokenInVerse = currentVerseSpec.StartWordIndex, EndTokenInVerse = currentVerseSpec.EndWordIndex, }); } - private NoteTitleAndContent ParseTitleAndContent(IReadOnlyList lines) + private static NoteTitleAndContent? ParseTitleAndContent(IReadOnlyList lines) { if (lines.Count == 0) { @@ -174,11 +176,11 @@ return result; } - private BibleNotesVerseSpecification GetVerseSpecification(string line) + private static BibleNotesVerseSpecification? GetVerseSpecification(string line) { var trimmed = line.Trim(); - if (!trimmed.StartsWith("[") || !trimmed.EndsWith("]")) + if (!trimmed.StartsWith("[", StringComparison.Ordinal) || !trimmed.EndsWith("]", StringComparison.Ordinal)) { return null; } @@ -251,7 +253,7 @@ } } - private string FindValue(string[] lines, string token) + private static string? FindValue(string[] lines, string token) { var line = lines.FirstOrDefault(x => x.Trim().StartsWith(token, StringComparison.OrdinalIgnoreCase)); diff --git a/JWLMerge.BackupFileServices/Helpers/Cleaner.cs b/JWLMerge.BackupFileServices/Helpers/Cleaner.cs index 926482d..0168364 100644 --- a/JWLMerge.BackupFileServices/Helpers/Cleaner.cs +++ b/JWLMerge.BackupFileServices/Helpers/Cleaner.cs @@ -2,13 +2,13 @@ { using System.Collections.Generic; using System.Linq; - using JWLMerge.BackupFileServices.Models.DatabaseModels; + using Models.DatabaseModels; using Serilog; /// /// Cleans jwlibrary files by removing redundant or anomalous database rows. /// - internal class Cleaner + internal sealed class Cleaner { private readonly Database _database; @@ -114,11 +114,10 @@ { int removed = 0; - var userMarkIdsFound = new HashSet(); - var ranges = _database.BlockRanges; if (ranges.Any()) { + var userMarkIdsFound = new HashSet(); var userMarkIds = GetUserMarkIdsInUse(); foreach (var range in Enumerable.Reverse(ranges)) diff --git a/JWLMerge.BackupFileServices/Helpers/DataAccessLayer.cs b/JWLMerge.BackupFileServices/Helpers/DataAccessLayer.cs index 0591721..98ea270 100644 --- a/JWLMerge.BackupFileServices/Helpers/DataAccessLayer.cs +++ b/JWLMerge.BackupFileServices/Helpers/DataAccessLayer.cs @@ -4,14 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; - using JWLMerge.BackupFileServices.Models.DatabaseModels; + using Models.DatabaseModels; using Serilog; /// /// Isolates all data access to the SQLite database embedded in /// jwlibrary files. /// - internal class DataAccessLayer + internal sealed class DataAccessLayer { private readonly string _databaseFilePath; @@ -27,13 +27,12 @@ public void CreateEmptyClone(string cloneFilePath) { Log.Logger.Debug($"Creating empty clone: {cloneFilePath}"); - - using (var source = CreateConnection(_databaseFilePath)) - using (var destination = CreateConnection(cloneFilePath)) - { - source.BackupDatabase(destination, "main", "main"); - ClearData(destination); - } + + using var source = CreateConnection(_databaseFilePath); + using var destination = CreateConnection(cloneFilePath); + + source.BackupDatabase(destination, "main", "main"); + ClearData(destination); } /// @@ -62,23 +61,22 @@ { var result = new Database(); - using (var connection = CreateConnection()) - { - result.InitBlank(); + using var connection = CreateConnection(); - result.LastModified.TimeLastModified = ReadAllRows(connection, ReadLastModified)?.FirstOrDefault()?.TimeLastModified; - result.Locations.AddRange(ReadAllRows(connection, ReadLocation)); - result.Notes.AddRange(ReadAllRows(connection, ReadNote)); - result.Tags.AddRange(ReadAllRows(connection, ReadTag)); - result.TagMaps.AddRange(ReadAllRows(connection, ReadTagMap)); - result.BlockRanges.AddRange(ReadAllRows(connection, ReadBlockRange)); - result.Bookmarks.AddRange(ReadAllRows(connection, ReadBookmark)); - result.UserMarks.AddRange(ReadAllRows(connection, ReadUserMark)); - result.InputFields.AddRange(ReadAllRows(connection, ReadInputField)); + result.InitBlank(); - // ensure bookmarks appear in similar order to original. - result.Bookmarks.Sort((bookmark1, bookmark2) => bookmark1.Slot.CompareTo(bookmark2.Slot)); - } + result.LastModified.TimeLastModified = ReadAllRows(connection, ReadLastModified).FirstOrDefault()?.TimeLastModified; + result.Locations.AddRange(ReadAllRows(connection, ReadLocation)); + result.Notes.AddRange(ReadAllRows(connection, ReadNote)); + result.Tags.AddRange(ReadAllRows(connection, ReadTag)); + result.TagMaps.AddRange(ReadAllRows(connection, ReadTagMap)); + result.BlockRanges.AddRange(ReadAllRows(connection, ReadBlockRange)); + result.Bookmarks.AddRange(ReadAllRows(connection, ReadBookmark)); + result.UserMarks.AddRange(ReadAllRows(connection, ReadUserMark)); + result.InputFields.AddRange(ReadAllRows(connection, ReadInputField)); + + // ensure bookmarks appear in similar order to original. + result.Bookmarks.Sort((bookmark1, bookmark2) => bookmark1.Slot.CompareTo(bookmark2.Slot)); return result; } @@ -87,26 +85,25 @@ SqliteConnection connection, Func readRowFunction) { - using (var cmd = connection.CreateCommand()) + using var cmd = connection.CreateCommand(); + + var result = new List(); + var tableName = typeof(TRowType).Name; + + cmd.CommandText = $"select * from {tableName}"; + Log.Logger.Debug($"SQL: {cmd.CommandText}"); + + using (var reader = cmd.ExecuteReader()) { - var result = new List(); - var tableName = typeof(TRowType).Name; - - cmd.CommandText = $"select * from {tableName}"; - Log.Logger.Debug($"SQL: {cmd.CommandText}"); - - using (var reader = cmd.ExecuteReader()) + while (reader.Read()) { - while (reader.Read()) - { - result.Add(readRowFunction(reader)); - } + result.Add(readRowFunction(reader)); } - - Log.Logger.Debug($"SQL result set count: {result.Count}"); - - return result; } + + Log.Logger.Debug($"SQL result set count: {result.Count}"); + + return result; } private static string ReadString(SqliteDataReader reader, string columnName) @@ -114,7 +111,7 @@ return reader[columnName].ToString(); } - private static string ReadNullableString(SqliteDataReader reader, string columnName) + private static string? ReadNullableString(SqliteDataReader reader, string columnName) { var value = reader[columnName]; return value == DBNull.Value ? null : value.ToString(); @@ -159,35 +156,32 @@ private static void VacuumDatabase(SqliteConnection connection) { - using (var command = connection.CreateCommand()) - { - command.CommandText = "vacuum;"; - Log.Logger.Debug($"SQL: {command.CommandText}"); + using var command = connection.CreateCommand(); - command.ExecuteNonQuery(); - } + command.CommandText = "vacuum;"; + Log.Logger.Debug($"SQL: {command.CommandText}"); + + command.ExecuteNonQuery(); } private static void UpdateLastModified(SqliteConnection connection) { - using (var command = connection.CreateCommand()) - { - command.CommandText = "delete from LastModified; insert into LastModified default values"; - Log.Logger.Debug($"SQL: {command.CommandText}"); + using var command = connection.CreateCommand(); - command.ExecuteNonQuery(); - } + command.CommandText = "delete from LastModified; insert into LastModified default values"; + Log.Logger.Debug($"SQL: {command.CommandText}"); + + command.ExecuteNonQuery(); } private static void ClearTable(SqliteConnection connection, string tableName) { - using (var command = connection.CreateCommand()) - { - command.CommandText = $"delete from {tableName}"; - Log.Logger.Debug($"SQL: {command.CommandText}"); + using var command = connection.CreateCommand(); - command.ExecuteNonQuery(); - } + command.CommandText = $"delete from {tableName}"; + Log.Logger.Debug($"SQL: {command.CommandText}"); + + command.ExecuteNonQuery(); } private static void PopulateTable(SqliteConnection connection, List rows) @@ -202,6 +196,11 @@ foreach (var row in rows) { + if (row == null) + { + continue; + } + using var cmd = connection.CreateCommand(); cmd.CommandText = $"insert into {tableName} ({columnNamesCsv}) values ({paramNamesCsv})"; AddPopulateTableParams(cmd, columnNames, paramNames, row); @@ -220,7 +219,7 @@ { for (int n = 0; n < columnNames.Count; ++n) { - var value = row.GetType().GetProperty(columnNames[n])?.GetValue(row) ?? DBNull.Value; + var value = row!.GetType().GetProperty(columnNames[n])?.GetValue(row) ?? DBNull.Value; cmd.Parameters.AddWithValue(paramNames[n], value); } } diff --git a/JWLMerge.BackupFileServices/Helpers/IdTranslator.cs b/JWLMerge.BackupFileServices/Helpers/IdTranslator.cs index a75e841..b410927 100644 --- a/JWLMerge.BackupFileServices/Helpers/IdTranslator.cs +++ b/JWLMerge.BackupFileServices/Helpers/IdTranslator.cs @@ -5,7 +5,7 @@ /// /// Used by the to map old and new id values./> /// - internal class IdTranslator + internal sealed class IdTranslator { private readonly Dictionary _ids; diff --git a/JWLMerge.BackupFileServices/Helpers/Merger.cs b/JWLMerge.BackupFileServices/Helpers/Merger.cs index 1451760..49d5491 100644 --- a/JWLMerge.BackupFileServices/Helpers/Merger.cs +++ b/JWLMerge.BackupFileServices/Helpers/Merger.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; - using JWLMerge.BackupFileServices.Events; - using JWLMerge.BackupFileServices.Models.DatabaseModels; + using Events; + using Models.DatabaseModels; using Serilog; /// @@ -25,7 +25,7 @@ private int _maxBlockRangeId; private int _maxBookmarkId; - public event EventHandler ProgressEvent; + public event EventHandler? ProgressEvent; /// /// Merges the specified databases. @@ -158,7 +158,7 @@ } } - private bool OverlappingBlockRanges(BlockRange blockRange1, BlockRange blockRange2) + private static bool OverlappingBlockRanges(BlockRange blockRange1, BlockRange blockRange2) { if (blockRange1.StartToken == blockRange2.StartToken && blockRange1.EndToken == blockRange2.EndToken) @@ -192,7 +192,7 @@ var tagId = _translatedTagIds.GetTranslatedId(sourceTagMap.TagId); var id = 0; - TagMap existingTagMap = null; + TagMap? existingTagMap = null; if (sourceTagMap.LocationId != null) { @@ -202,8 +202,11 @@ { // must add location... var location = source.FindLocation(sourceTagMap.LocationId.Value); - InsertLocation(location, destination); - id = _translatedLocationIds.GetTranslatedId(sourceTagMap.LocationId.Value); + if (location != null) + { + InsertLocation(location, destination); + id = _translatedLocationIds.GetTranslatedId(sourceTagMap.LocationId.Value); + } } existingTagMap = destination.FindTagMapForLocation(tagId, id); @@ -224,7 +227,7 @@ NormaliseTagMapPositions(destination.TagMaps); } - private void NormaliseTagMapPositions(List entries) + private static void NormaliseTagMapPositions(List entries) { // there is unique constraint on TagId, Position var tmpStorage = entries.GroupBy(x => x.TagId).ToDictionary(x => x.Key); @@ -273,8 +276,11 @@ { var referencedLocation = sourceUserMark.LocationId; var location = source.FindLocation(referencedLocation); + if (location != null) + { + InsertLocation(location, destination); + } - InsertLocation(location, destination); InsertUserMark(sourceUserMark, destination); } } @@ -300,7 +306,7 @@ } } - private void InsertInputField(InputField inputField, int locationId, Database destination) + private static void InsertInputField(InputField inputField, int locationId, Database destination) { var inputFldClone = inputField.Clone(); inputFldClone.LocationId = locationId; @@ -438,8 +444,10 @@ { var referencedLocation = note.LocationId.Value; var location = source.FindLocation(referencedLocation); - - InsertLocation(location, destination); + if (location != null) + { + InsertLocation(location, destination); + } } InsertNote(note, destination); @@ -447,7 +455,7 @@ } } - private void UpdateNote(Note source, Note destination) + private static void UpdateNote(Note source, Note destination) { destination.Title = source.Title; destination.Content = source.Content; @@ -456,7 +464,7 @@ private void OnProgressEvent(string message) { - ProgressEvent?.Invoke(this, new ProgressEventArgs { Message = message }); + ProgressEvent?.Invoke(this, new ProgressEventArgs(message)); } private void ProgressMessage(string logMessage) diff --git a/JWLMerge.BackupFileServices/Helpers/NotesImporter.cs b/JWLMerge.BackupFileServices/Helpers/NotesImporter.cs index cd5f608..7ad9cf0 100644 --- a/JWLMerge.BackupFileServices/Helpers/NotesImporter.cs +++ b/JWLMerge.BackupFileServices/Helpers/NotesImporter.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.DatabaseModels; + using Models; + using Models.DatabaseModels; internal sealed class NotesImporter { @@ -74,7 +74,7 @@ } else { - if (!existingNote.Content.Equals(note.NoteContent)) + if (NoteIsDifferent(existingNote, note.NoteContent)) { // need to update the note. result.BibleNotesUpdated++; @@ -92,6 +92,16 @@ return result; } + private static bool NoteIsDifferent(Note existingNote, string? newNote) + { + if (string.IsNullOrEmpty(existingNote.Content)) + { + return !string.IsNullOrEmpty(newNote); + } + + return !existingNote.Content.Equals(newNote); + } + private void InsertNote(BibleNote note) { var book = note.BookChapterAndVerse.BookNumber; @@ -100,7 +110,7 @@ var location = _targetDatabase.FindLocationByBibleChapter(_bibleKeySymbol, book, chapter) ?? InsertLocation(book, chapter); - UserMark userMark = null; + UserMark? userMark = null; if (note.StartTokenInVerse != null && note.EndTokenInVerse != null) { // the note should be associated with some @@ -185,7 +195,7 @@ return userMark; } - private UserMark FindExistingUserMark(int locationId, int startToken, int endToken) + private UserMark? FindExistingUserMark(int locationId, int startToken, int endToken) { var userMarksForLocation = _targetDatabase.FindUserMarks(locationId); if (userMarksForLocation == null) @@ -228,7 +238,7 @@ return location; } - private Note FindExistingNote(Database database, BibleNote note) + private static Note? FindExistingNote(Database database, BibleNote note) { var existingVerseNotes = database.FindNotes(note.BookChapterAndVerse); return existingVerseNotes?.FirstOrDefault(verseNote => verseNote.Title == note.NoteTitle); diff --git a/JWLMerge.BackupFileServices/Helpers/RedactService.cs b/JWLMerge.BackupFileServices/Helpers/RedactService.cs index 888dc4e..e83c667 100644 --- a/JWLMerge.BackupFileServices/Helpers/RedactService.cs +++ b/JWLMerge.BackupFileServices/Helpers/RedactService.cs @@ -4,7 +4,7 @@ using System.Text; // ReSharper disable once ClassNeverInstantiated.Global - internal class RedactService + internal sealed class RedactService { private readonly Lazy _loremIpsumLines; private readonly Random _random = new Random(); @@ -32,7 +32,7 @@ { if (sb.Length > 0) { - sb.Append(" "); + sb.Append(' '); } sb.Append(GetRandomSentence()); diff --git a/JWLMerge.BackupFileServices/IBackupFileService.cs b/JWLMerge.BackupFileServices/IBackupFileService.cs index cb67b7a..05d58de 100644 --- a/JWLMerge.BackupFileServices/IBackupFileService.cs +++ b/JWLMerge.BackupFileServices/IBackupFileService.cs @@ -2,10 +2,10 @@ { using System; using System.Collections.Generic; - using JWLMerge.BackupFileServices.Events; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.ExcelServices; + using Events; + using Models; + using Models.DatabaseModels; + using ExcelServices; /// /// The BackupFileService interface. @@ -71,7 +71,7 @@ /// Number of notes removed. int RemoveNotesByTag( BackupFile backup, - int[] tagIds, + int[]? tagIds, bool removeUntaggedNotes, bool removeAssociatedUnderlining, bool removeAssociatedTags); @@ -83,7 +83,7 @@ /// The color indexes to target. /// Whether associated notes should also be removed. /// Number of underlined items removed. - int RemoveUnderliningByColour(BackupFile backup, int[] colorIndexes, bool removeAssociatedNotes); + int RemoveUnderliningByColour(BackupFile backup, int[]? colorIndexes, bool removeAssociatedNotes); /// /// Removes underlining by publication and colour. @@ -99,7 +99,7 @@ BackupFile backup, int colorIndex, bool anyColor, - string publicationSymbol, + string? publicationSymbol, bool anyPublication, bool removeAssociatedNotes); diff --git a/JWLMerge.BackupFileServices/JWLMerge.BackupFileServices.csproj b/JWLMerge.BackupFileServices/JWLMerge.BackupFileServices.csproj index 05a39d5..220e329 100644 --- a/JWLMerge.BackupFileServices/JWLMerge.BackupFileServices.csproj +++ b/JWLMerge.BackupFileServices/JWLMerge.BackupFileServices.csproj @@ -14,6 +14,10 @@ + + + + diff --git a/JWLMerge.BackupFileServices/Models/BackupFile.cs b/JWLMerge.BackupFileServices/Models/BackupFile.cs index eaeebad..fd3c1ad 100644 --- a/JWLMerge.BackupFileServices/Models/BackupFile.cs +++ b/JWLMerge.BackupFileServices/Models/BackupFile.cs @@ -2,7 +2,7 @@ { using System.ComponentModel; using System.Runtime.CompilerServices; - using JWLMerge.BackupFileServices.Models.ManifestFile; + using ManifestFile; /// /// The Backup file. @@ -10,15 +10,15 @@ /// We implement INotifyPropertyChanged to prevent the common "WPF binding leak". public sealed class BackupFile : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; - public Manifest Manifest { get; set; } + public Manifest Manifest { get; set; } = null!; - public DatabaseModels.Database Database { get; set; } + public DatabaseModels.Database Database { get; set; } = null!; #pragma warning disable IDE0051 // Remove unused private members #pragma warning disable S1144 // Remove unused private members - private void OnPropertyChanged([CallerMemberName] string propertyName = null) + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) #pragma warning restore S1144 // Remove unused private members #pragma warning restore IDE0051 // Remove unused private members { diff --git a/JWLMerge.BackupFileServices/Models/BibleBookChapterAndVerse.cs b/JWLMerge.BackupFileServices/Models/BibleBookChapterAndVerse.cs index 5dd35ca..55033dd 100644 --- a/JWLMerge.BackupFileServices/Models/BibleBookChapterAndVerse.cs +++ b/JWLMerge.BackupFileServices/Models/BibleBookChapterAndVerse.cs @@ -2,7 +2,7 @@ { using System; - public struct BibleBookChapterAndVerse : IEquatable + public readonly struct BibleBookChapterAndVerse : IEquatable { public BibleBookChapterAndVerse(int bookNum, int chapterNum, int verseNum) { diff --git a/JWLMerge.BackupFileServices/Models/BibleNotesVerseSpecification.cs b/JWLMerge.BackupFileServices/Models/BibleNotesVerseSpecification.cs index bbc7457..222bfaf 100644 --- a/JWLMerge.BackupFileServices/Models/BibleNotesVerseSpecification.cs +++ b/JWLMerge.BackupFileServices/Models/BibleNotesVerseSpecification.cs @@ -1,6 +1,6 @@ namespace JWLMerge.BackupFileServices.Models { - internal class BibleNotesVerseSpecification + internal sealed class BibleNotesVerseSpecification { public int BookNumber { get; set; } diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/BibleNote.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/BibleNote.cs index f5d65d5..f862931 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/BibleNote.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/BibleNote.cs @@ -10,8 +10,8 @@ public int ColourIndex { get; set; } - public string NoteTitle { get; set; } + public string? NoteTitle { get; set; } - public string NoteContent { get; set; } + public string? NoteContent { get; set; } } } diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/Bookmark.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/Bookmark.cs index 11753c2..f2ae149 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/Bookmark.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/Bookmark.cs @@ -28,12 +28,12 @@ /// /// The title text. /// - public string Title { get; set; } + public string Title { get; set; } = null!; /// /// A snippet of the bookmarked text (can be null) /// - public string Snippet { get; set; } + public string? Snippet { get; set; } /// /// The block type. Compare Locations.cs > BlockType diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/Database.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/Database.cs index ad4a0ed..7c3043e 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/Database.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/Database.cs @@ -3,29 +3,29 @@ using System; using System.Collections.Generic; using System.Linq; - using JWLMerge.BackupFileServices.Exceptions; + using Exceptions; using Serilog; public class Database { private readonly Dictionary _bookmarkSlots = new Dictionary(); - private Lazy> _notesGuidIndex; - private Lazy> _notesIdIndex; - private Lazy>> _inputFieldsIndex; - private Lazy>> _notesVerseIndex; - private Lazy> _userMarksGuidIndex; - private Lazy> _userMarksIdIndex; - private Lazy>> _userMarksLocationIdIndex; - private Lazy> _locationsIdIndex; - private Lazy> _locationsValueIndex; - private Lazy> _locationsBibleChapterIndex; - private Lazy> _tagsNameIndex; - private Lazy> _tagsIdIndex; - private Lazy> _tagMapNoteIndex; - private Lazy> _tagMapLocationIndex; - private Lazy>> _blockRangesUserMarkIdIndex; - private Lazy> _bookmarksIndex; + private Lazy> _notesGuidIndex = null!; + private Lazy> _notesIdIndex = null!; + private Lazy>> _inputFieldsIndex = null!; + private Lazy>> _notesVerseIndex = null!; + private Lazy> _userMarksGuidIndex = null!; + private Lazy> _userMarksIdIndex = null!; + private Lazy>> _userMarksLocationIdIndex = null!; + private Lazy> _locationsIdIndex = null!; + private Lazy> _locationsValueIndex = null!; + private Lazy> _locationsBibleChapterIndex = null!; + private Lazy> _tagsNameIndex = null!; + private Lazy> _tagsIdIndex = null!; + private Lazy> _tagMapNoteIndex = null!; + private Lazy> _tagMapLocationIndex = null!; + private Lazy>> _blockRangesUserMarkIdIndex = null!; + private Lazy> _bookmarksIndex = null!; public Database() { @@ -99,7 +99,7 @@ public void AddBibleNoteAndUpdateIndex( BibleBookChapterAndVerse verseRef, Note value, - TagMap tagMap) + TagMap? tagMap) { if (value == null) { @@ -226,63 +226,63 @@ } } - public Note FindNote(string noteGuid) + public Note? FindNote(string noteGuid) { return _notesGuidIndex.Value.TryGetValue(noteGuid, out var note) ? note : null; } - public Note FindNote(int noteId) + public Note? FindNote(int noteId) { return _notesIdIndex.Value.TryGetValue(noteId, out var note) ? note : null; } - public IEnumerable FindNotes(BibleBookChapterAndVerse verseRef) + public IEnumerable? FindNotes(BibleBookChapterAndVerse verseRef) { return _notesVerseIndex.Value.TryGetValue(verseRef, out var notes) ? notes : null; } - public UserMark FindUserMark(string userMarkGuid) + public UserMark? FindUserMark(string userMarkGuid) { return _userMarksGuidIndex.Value.TryGetValue(userMarkGuid, out var userMark) ? userMark : null; } - public UserMark FindUserMark(int userMarkId) + public UserMark? FindUserMark(int userMarkId) { return _userMarksIdIndex.Value.TryGetValue(userMarkId, out var userMark) ? userMark : null; } - public IEnumerable FindUserMarks(int locationId) + public IEnumerable? FindUserMarks(int locationId) { return _userMarksLocationIdIndex.Value.TryGetValue(locationId, out var userMarks) ? userMarks : null; } - public Tag FindTag(int tagType, string tagName) + public Tag? FindTag(int tagType, string tagName) { var key = new TagTypeAndName(tagType, tagName); return _tagsNameIndex.Value.TryGetValue(key, out var tag) ? tag : null; } - public Tag FindTag(int tagId) + public Tag? FindTag(int tagId) { return _tagsIdIndex.Value.TryGetValue(tagId, out var tag) ? tag : null; } - public TagMap FindTagMapForNote(int tagId, int noteId) + public TagMap? FindTagMapForNote(int tagId, int noteId) { return _tagMapNoteIndex.Value.TryGetValue(GetTagMapNoteKey(tagId, noteId), out var tag) ? tag : null; } - public TagMap FindTagMapForLocation(int tagId, int locationId) + public TagMap? FindTagMapForLocation(int tagId, int locationId) { return _tagMapLocationIndex.Value.TryGetValue(GetTagMapLocationKey(tagId, locationId), out var tag) ? tag : null; } - public Location FindLocation(int locationId) + public Location? FindLocation(int locationId) { return _locationsIdIndex.Value.TryGetValue(locationId, out var location) ? location : null; } - public InputField FindInputField(int locationId, string textTag) + public InputField? FindInputField(int locationId, string textTag) { if (!_inputFieldsIndex.Value.TryGetValue(locationId, out var list)) { @@ -292,7 +292,7 @@ return list.SingleOrDefault(x => x.TextTag.Equals(textTag, StringComparison.OrdinalIgnoreCase)); } - public Location FindLocationByValues(Location locationValues) + public Location? FindLocationByValues(Location locationValues) { if (locationValues == null) { @@ -303,18 +303,18 @@ return _locationsValueIndex.Value.TryGetValue(key, out var location) ? location : null; } - public Location FindLocationByBibleChapter(string bibleKeySymbol, int bibleBookNumber, int bibleChapter) + public Location? FindLocationByBibleChapter(string bibleKeySymbol, int bibleBookNumber, int bibleChapter) { var key = GetLocationByBibleChapterKey(bibleBookNumber, bibleChapter, bibleKeySymbol); return _locationsBibleChapterIndex.Value.TryGetValue(key, out var location) ? location : null; } - public IReadOnlyCollection FindBlockRanges(int userMarkId) + public IReadOnlyCollection? FindBlockRanges(int userMarkId) { return _blockRangesUserMarkIdIndex.Value.TryGetValue(userMarkId, out var ranges) ? ranges : null; } - public Bookmark FindBookmark(int locationId, int publicationLocationId) + public Bookmark? FindBookmark(int locationId, int publicationLocationId) { string key = GetBookmarkKey(locationId, publicationLocationId); return _bookmarksIndex.Value.TryGetValue(key, out var bookmark) ? bookmark : null; @@ -483,19 +483,19 @@ return result; } - private string GetBookmarkKey(int locationId, int publicationLocationId) + private static string GetBookmarkKey(int locationId, int publicationLocationId) { return $"{locationId}-{publicationLocationId}"; } - private string GetLocationByValueKey(Location location) + private static string GetLocationByValueKey(Location location) { return $"{location.KeySymbol}|{location.IssueTagNumber}|{location.MepsLanguage}|{location.Type}|{location.BookNumber ?? -1}|{location.ChapterNumber ?? -1}|{location.DocumentId ?? -1}|{location.Track ?? -1}"; } - private string GetLocationByBibleChapterKey(int bibleBookNumber, int chapterNumber, string bibleKeySymbol) + private static string GetLocationByBibleChapterKey(int bibleBookNumber, int chapterNumber, string? bibleKeySymbol) { - return $"{bibleBookNumber}-{chapterNumber}-{bibleKeySymbol}"; + return $"{bibleBookNumber}-{chapterNumber}-{bibleKeySymbol ?? string.Empty}"; } private Dictionary BookmarkIndexValueFactory() @@ -524,12 +524,12 @@ return Tags.ToDictionary(tag => tag.TagId); } - private string GetTagMapNoteKey(int tagId, int noteId) + private static string GetTagMapNoteKey(int tagId, int noteId) { return $"{tagId}-{noteId}"; } - private string GetTagMapLocationKey(int tagId, int locationId) + private static string GetTagMapLocationKey(int tagId, int locationId) { return $"{tagId}-{locationId}"; } diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/InputField.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/InputField.cs index cb81db8..fffbcae 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/InputField.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/InputField.cs @@ -4,9 +4,9 @@ { public int LocationId { get; set; } - public string TextTag { get; set; } + public string TextTag { get; set; } = null!; - public string Value { get; set; } + public string Value { get; set; } = null!; public InputField Clone() { diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/LastModified.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/LastModified.cs index 46ad149..90215e5 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/LastModified.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/LastModified.cs @@ -8,7 +8,7 @@ /// Time stamp when the database was last modified. /// [JsonProperty(PropertyName = "LastModified")] - public string TimeLastModified { get; set; } + public string? TimeLastModified { get; set; } public void Reset() { diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/Location.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/Location.cs index 7135e17..2bf59af 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/Location.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/Location.cs @@ -38,7 +38,7 @@ /// /// The JWL publication key symbol (nullable). /// - public string KeySymbol { get; set; } + public string? KeySymbol { get; set; } /// /// The MEPS identifier for the publication language. @@ -57,7 +57,7 @@ /// /// A location title (nullable). /// - public string Title { get; set; } + public string? Title { get; set; } public Location Clone() { diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/Note.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/Note.cs index dbbc86a..bceadd9 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/Note.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/Note.cs @@ -12,7 +12,7 @@ /// /// A Guid (that should assist in merging notes). /// - public string Guid { get; set; } + public string Guid { get; set; } = null!; /// /// The user mark identifier (if the note is associated with user-highlighting). @@ -28,17 +28,17 @@ /// /// The user-defined note title. /// - public string Title { get; set; } + public string? Title { get; set; } /// /// The user-defined note content. /// - public string Content { get; set; } + public string? Content { get; set; } /// /// Time stamp when the note was last edited. ISO 8601 format. /// - public string LastModified { get; set; } + public string? LastModified { get; set; } /// /// The type of block associated with the note. diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/Tag.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/Tag.cs index 197c089..c34f1d5 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/Tag.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/Tag.cs @@ -16,13 +16,13 @@ /// /// The name of the tag. /// - public string Name { get; set; } + public string Name { get; set; } = null!; /// /// The optional image file name. /// /// Added in db ver 7 April 2020. - public string ImageFileName { get; set; } + public string? ImageFileName { get; set; } public Tag Clone() { diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/TagMap.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/TagMap.cs index 8e5a9cf..1101105 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/TagMap.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/TagMap.cs @@ -18,19 +18,19 @@ /// /// Playlist Item Id. /// - /// added in in db v7, Apr 2020 + /// added in db v7, Apr 2020 public int? PlaylistItemId { get; set; } /// /// Location Id. /// - /// added in in db v7, Apr 2020 + /// added in db v7, Apr 2020 public int? LocationId { get; set; } /// /// Note Id. /// - /// added in in db v7, Apr 2020. + /// added in db v7, Apr 2020. public int? NoteId { get; set; } /// diff --git a/JWLMerge.BackupFileServices/Models/DatabaseModels/UserMark.cs b/JWLMerge.BackupFileServices/Models/DatabaseModels/UserMark.cs index 5935bd2..c4bf39e 100644 --- a/JWLMerge.BackupFileServices/Models/DatabaseModels/UserMark.cs +++ b/JWLMerge.BackupFileServices/Models/DatabaseModels/UserMark.cs @@ -27,7 +27,7 @@ /// /// The guid. Useful in merging! /// - public string UserMarkGuid { get; set; } + public string UserMarkGuid { get; set; } = null!; /// /// The highlight version. Semantics unknown! diff --git a/JWLMerge.BackupFileServices/Models/ManifestFile/Manifest.cs b/JWLMerge.BackupFileServices/Models/ManifestFile/Manifest.cs index d9f1d46..b54342e 100644 --- a/JWLMerge.BackupFileServices/Models/ManifestFile/Manifest.cs +++ b/JWLMerge.BackupFileServices/Models/ManifestFile/Manifest.cs @@ -9,17 +9,17 @@ /// We implement INotifyPropertyChanged to prevent the common "WPF binding leak". public sealed class Manifest : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; /// /// The name of the backup file (without the "jwlibrary" extension). /// - public string Name { get; set; } + public string Name { get; set; } = null!; /// /// The local creation date in the form "YYYY-MM-DD" /// - public string CreationDate { get; set; } + public string CreationDate { get; set; } = null!; /// /// The manifest schema version. @@ -34,7 +34,7 @@ /// /// Details of the backup database. /// - public UserDataBackup UserDataBackup { get; set; } + public UserDataBackup UserDataBackup { get; set; } = null!; public Manifest Clone() { @@ -43,7 +43,7 @@ #pragma warning disable IDE0051 // Remove unused private members #pragma warning disable S1144 // Remove unused private members - private void OnPropertyChanged([CallerMemberName] string propertyName = null) + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) #pragma warning restore S1144 // Remove unused private members #pragma warning restore IDE0051 // Remove unused private members { diff --git a/JWLMerge.BackupFileServices/Models/ManifestFile/UserDataBackup.cs b/JWLMerge.BackupFileServices/Models/ManifestFile/UserDataBackup.cs index 713413b..9006d5d 100644 --- a/JWLMerge.BackupFileServices/Models/ManifestFile/UserDataBackup.cs +++ b/JWLMerge.BackupFileServices/Models/ManifestFile/UserDataBackup.cs @@ -10,28 +10,28 @@ /// We implement INotifyPropertyChanged to prevent the common "WPF binding leak". public sealed class UserDataBackup : INotifyPropertyChanged { - public event PropertyChangedEventHandler PropertyChanged; + public event PropertyChangedEventHandler? PropertyChanged; /// /// The last modified date of the database in ISO 8601, e.g. "2018-01-17T14:37:27+00:00" /// Corresponds to the value in the LastModifiedDate table. /// - public string LastModifiedDate { get; set; } + public string LastModifiedDate { get; set; } = null!; /// /// The name of the source device (e.g. the name of the PC). /// - public string DeviceName { get; set; } + public string DeviceName { get; set; } = null!; /// /// The database name (always "userData.db"?) /// - public string DatabaseName { get; set; } + public string DatabaseName { get; set; } = null!; /// /// A sha256 hash of the associated database file. /// - public string Hash { get; set; } + public string Hash { get; set; } = null!; /// /// The database schema version. @@ -41,7 +41,7 @@ #pragma warning disable IDE0051 // Remove unused private members #pragma warning disable S1144 // Remove unused private members - private void OnPropertyChanged([CallerMemberName] string propertyName = null) + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) #pragma warning restore S1144 // Remove unused private members #pragma warning restore IDE0051 // Remove unused private members { diff --git a/JWLMerge.BackupFileServices/Models/NoteTitleAndContent.cs b/JWLMerge.BackupFileServices/Models/NoteTitleAndContent.cs index 713003f..5daf24b 100644 --- a/JWLMerge.BackupFileServices/Models/NoteTitleAndContent.cs +++ b/JWLMerge.BackupFileServices/Models/NoteTitleAndContent.cs @@ -1,9 +1,9 @@ namespace JWLMerge.BackupFileServices.Models { - internal class NoteTitleAndContent + internal sealed class NoteTitleAndContent { - public string Title { get; set; } + public string? Title { get; set; } - public string Content { get; set; } + public string? Content { get; set; } } } diff --git a/JWLMerge.BackupFileServices/Models/TagTypeAndName.cs b/JWLMerge.BackupFileServices/Models/TagTypeAndName.cs index bb760b1..02412c9 100644 --- a/JWLMerge.BackupFileServices/Models/TagTypeAndName.cs +++ b/JWLMerge.BackupFileServices/Models/TagTypeAndName.cs @@ -1,6 +1,6 @@ namespace JWLMerge.BackupFileServices.Models { - internal class TagTypeAndName + internal sealed class TagTypeAndName { public TagTypeAndName(int type, string name) { diff --git a/JWLMerge.ExcelServices/ExcelService.cs b/JWLMerge.ExcelServices/ExcelService.cs index 5138a75..5e802b0 100644 --- a/JWLMerge.ExcelServices/ExcelService.cs +++ b/JWLMerge.ExcelServices/ExcelService.cs @@ -13,7 +13,11 @@ namespace JWLMerge.ExcelServices private const string WorkbookName = "Notes"; // returns the last row written - public int AppendToBibleNotesFile(string excelFilePath, IReadOnlyCollection notes, int startRow, string backupFilePath) + public int AppendToBibleNotesFile( + string excelFilePath, + IReadOnlyCollection? notes, + int startRow, + string backupFilePath) { if (string.IsNullOrEmpty(excelFilePath)) { @@ -30,73 +34,71 @@ namespace JWLMerge.ExcelServices { return startRow; } - - using (var workbook = new XLWorkbook(excelFilePath)) + + using var workbook = new XLWorkbook(excelFilePath); + + if (!workbook.Worksheets.TryGetWorksheet(WorkbookName, out var worksheet)) { - if (!workbook.Worksheets.TryGetWorksheet(WorkbookName, out var worksheet)) - { - throw new ExcelServicesException("Could not find worksheet!"); - } - - var row = startRow; - foreach (var note in notes) - { - SetCellStringValue(worksheet, row, 1, note.PubSymbol); - SetCellStringValue(worksheet, row, 2, note.BookName); - SetCellIntegerValue(worksheet, row, 3, note.BookNumber); - SetCellIntegerValue(worksheet, row, 4, note.ChapterNumber); - SetCellIntegerValue(worksheet, row, 5, note.VerseNumber); - SetCellStringValue(worksheet, row, 6, note.ChapterAndVerseString); - SetCellStringValue(worksheet, row, 7, note.BookNameChapterAndVerseString); - SetCellIntegerValue(worksheet, row, 8, note.ColorCode); - SetCellStringValue(worksheet, row, 9, note.TagsCsv); - SetCellStringValue(worksheet, row, 10, note.NoteTitle); - SetCellStringValue(worksheet, row, 11, note.NoteContent); - - ++row; - } - - workbook.Save(); - - return row; + throw new ExcelServicesException("Could not find worksheet!"); } + + var row = startRow; + foreach (var note in notes) + { + SetCellStringValue(worksheet, row, 1, note.PubSymbol); + SetCellStringValue(worksheet, row, 2, note.BookName); + SetCellIntegerValue(worksheet, row, 3, note.BookNumber); + SetCellIntegerValue(worksheet, row, 4, note.ChapterNumber); + SetCellIntegerValue(worksheet, row, 5, note.VerseNumber); + SetCellStringValue(worksheet, row, 6, note.ChapterAndVerseString); + SetCellStringValue(worksheet, row, 7, note.BookNameChapterAndVerseString); + SetCellIntegerValue(worksheet, row, 8, note.ColorCode); + SetCellStringValue(worksheet, row, 9, note.TagsCsv); + SetCellStringValue(worksheet, row, 10, note.NoteTitle); + SetCellStringValue(worksheet, row, 11, note.NoteContent); + + ++row; + } + + workbook.Save(); + + return row; } - private void SetCellStringValue(IXLWorksheet worksheet, int row, int col, string value) + private static void SetCellStringValue(IXLWorksheet worksheet, int row, int col, string? value) { worksheet.Cell(row, col).DataType = XLDataType.Text; - worksheet.Cell(row, col).SetValue(value); + worksheet.Cell(row, col).SetValue(value ?? string.Empty); } - private void SetCellIntegerValue(IXLWorksheet worksheet, int row, int col, int? value) + private static void SetCellIntegerValue(IXLWorksheet worksheet, int row, int col, int? value) { worksheet.Cell(row, col).DataType = XLDataType.Number; worksheet.Cell(row, col).SetValue(value); } - private int GenerateHeader(string excelFilePath, string backupFilePath) + private static int GenerateHeader(string excelFilePath, string backupFilePath) { - using (var workbook = new XLWorkbook()) - { - var worksheet = workbook.Worksheets.Add(WorkbookName); - worksheet.Cell("A1").Value = "Bible Notes Extracted from JWL Backup File"; - worksheet.Cell("A2").Value = $"Backup file: {backupFilePath}"; - worksheet.Cell("A3").Value = $"Exported: {DateTime.Now.ToShortDateString()}"; + using var workbook = new XLWorkbook(); - worksheet.Cell("A5").Value = "Symbol"; - worksheet.Cell("B5").Value = "Book"; - worksheet.Cell("C5").Value = "BookNumber"; - worksheet.Cell("D5").Value = "Chapter"; - worksheet.Cell("E5").Value = "Verse"; - worksheet.Cell("F5").Value = "ChapterAndVerse"; - worksheet.Cell("G5").Value = "FullRef"; - worksheet.Cell("H5").Value = "Color"; - worksheet.Cell("I5").Value = "Tags"; - worksheet.Cell("J5").Value = "Title"; - worksheet.Cell("K5").Value = "Content"; + var worksheet = workbook.Worksheets.Add(WorkbookName); + worksheet.Cell("A1").Value = "Bible Notes Extracted from JWL Backup File"; + worksheet.Cell("A2").Value = $"Backup file: {backupFilePath}"; + worksheet.Cell("A3").Value = $"Exported: {DateTime.Now.ToShortDateString()}"; - workbook.SaveAs(excelFilePath); - } + worksheet.Cell("A5").Value = "Symbol"; + worksheet.Cell("B5").Value = "Book"; + worksheet.Cell("C5").Value = "BookNumber"; + worksheet.Cell("D5").Value = "Chapter"; + worksheet.Cell("E5").Value = "Verse"; + worksheet.Cell("F5").Value = "ChapterAndVerse"; + worksheet.Cell("G5").Value = "FullRef"; + worksheet.Cell("H5").Value = "Color"; + worksheet.Cell("I5").Value = "Tags"; + worksheet.Cell("J5").Value = "Title"; + worksheet.Cell("K5").Value = "Content"; + + workbook.SaveAs(excelFilePath); // return last row used. return 4; diff --git a/JWLMerge.ExcelServices/IExcelService.cs b/JWLMerge.ExcelServices/IExcelService.cs index ff16f49..7c66285 100644 --- a/JWLMerge.ExcelServices/IExcelService.cs +++ b/JWLMerge.ExcelServices/IExcelService.cs @@ -5,6 +5,10 @@ namespace JWLMerge.ExcelServices { public interface IExcelService { - int AppendToBibleNotesFile(string excelFilePath, IReadOnlyCollection notes, int startRow, string backupFilePath); + int AppendToBibleNotesFile( + string excelFilePath, + IReadOnlyCollection? notes, + int startRow, + string backupFilePath); } } diff --git a/JWLMerge.ExcelServices/JWLMerge.ExcelServices.csproj b/JWLMerge.ExcelServices/JWLMerge.ExcelServices.csproj index 2d1cfd8..53fa2f4 100644 --- a/JWLMerge.ExcelServices/JWLMerge.ExcelServices.csproj +++ b/JWLMerge.ExcelServices/JWLMerge.ExcelServices.csproj @@ -18,4 +18,8 @@ + + + + diff --git a/JWLMerge.ExcelServices/Models/BibleNote.cs b/JWLMerge.ExcelServices/Models/BibleNote.cs index 33a1045..a4102d6 100644 --- a/JWLMerge.ExcelServices/Models/BibleNote.cs +++ b/JWLMerge.ExcelServices/Models/BibleNote.cs @@ -2,23 +2,29 @@ { public class BibleNote { - public int BookNumber { get; set; } + public BibleNote(int bookNumber, string bookName) + { + BookNumber = bookNumber; + BookName = bookName; + } - public string BookName { get; set; } + public int BookNumber { get; } + + public string BookName { get; } public int? ChapterNumber { get; set; } public int? VerseNumber { get; set; } - public string NoteTitle { get; set; } + public string? NoteTitle { get; set; } - public string NoteContent { get; set; } + public string? NoteContent { get; set; } - public string TagsCsv { get; set; } + public string? TagsCsv { get; set; } public int? ColorCode { get; set; } - public string PubSymbol { get; set; } + public string? PubSymbol { get; set; } public string ChapterAndVerseString { diff --git a/JWLMerge.Tests/JWLMerge.Tests.csproj b/JWLMerge.Tests/JWLMerge.Tests.csproj index 5bd2310..dc33ebd 100644 --- a/JWLMerge.Tests/JWLMerge.Tests.csproj +++ b/JWLMerge.Tests/JWLMerge.Tests.csproj @@ -7,6 +7,10 @@ false + + + + diff --git a/JWLMerge.Tests/TestBase.cs b/JWLMerge.Tests/TestBase.cs index d5955b8..16aa48a 100644 --- a/JWLMerge.Tests/TestBase.cs +++ b/JWLMerge.Tests/TestBase.cs @@ -2,24 +2,24 @@ { using System; using System.Collections.Generic; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.BackupFileServices.Models.ManifestFile; + using BackupFileServices.Models; + using BackupFileServices.Models.DatabaseModels; + using BackupFileServices.Models.ManifestFile; public class TestBase { - private readonly Random _random = new Random(); + private readonly Random _random = new(); protected BackupFile CreateMockBackup(int numRecords = 100) { - return new BackupFile + return new() { Manifest = CreateMockManifest(), Database = CreateMockDatabase(numRecords), }; } - protected Database CreateMockDatabase(int numRecords) + private Database CreateMockDatabase(int numRecords) { var result = new Database(); @@ -34,7 +34,7 @@ return result; } - protected Manifest CreateMockManifest() + private static Manifest CreateMockManifest() { DateTime now = DateTime.Now; string simpleDateString = $"{now.Year}-{now.Month:D2}-{now.Day:D2}"; @@ -55,7 +55,7 @@ }; } - private List CreateMockBlockRanges(int numRecords) + private static List CreateMockBlockRanges(int numRecords) { var result = new List(); @@ -97,7 +97,7 @@ return result; } - private List CreateMockUserMarks(int numRecords) + private static List CreateMockUserMarks(int numRecords) { var result = new List(); diff --git a/JWLMerge.Tests/TestCleaner.cs b/JWLMerge.Tests/TestCleaner.cs index 3c7f820..a3ba85b 100644 --- a/JWLMerge.Tests/TestCleaner.cs +++ b/JWLMerge.Tests/TestCleaner.cs @@ -1,8 +1,7 @@ namespace JWLMerge.Tests { - using JWLMerge.BackupFileServices.Helpers; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.ManifestFile; + using BackupFileServices.Helpers; + using BackupFileServices.Models.ManifestFile; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -11,11 +10,11 @@ [TestMethod] public void TestAllClean() { - BackupFile file = CreateMockBackup(); + var file = CreateMockBackup(); file.Manifest = new Manifest(); - Cleaner cleaner = new Cleaner(file.Database); - int removedRows = cleaner.Clean(); + var cleaner = new Cleaner(file.Database); + var removedRows = cleaner.Clean(); Assert.AreEqual(0, removedRows); } diff --git a/JWLMerge.Tests/TestImportNotes.cs b/JWLMerge.Tests/TestImportNotes.cs index df72f3e..0623a9b 100644 --- a/JWLMerge.Tests/TestImportNotes.cs +++ b/JWLMerge.Tests/TestImportNotes.cs @@ -2,9 +2,9 @@ { using System.Collections.Generic; using System.Linq; - using JWLMerge.BackupFileServices.Helpers; - using JWLMerge.BackupFileServices.Models; - using JWLMerge.BackupFileServices.Models.DatabaseModels; + using BackupFileServices.Helpers; + using BackupFileServices.Models; + using BackupFileServices.Models.DatabaseModels; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -13,13 +13,14 @@ [TestMethod] public void TestImport1() { - int numRecords = 100; + const int numRecords = 100; var file1 = CreateMockBackup(numRecords); var notes = CreateMockBibleNotes().ToArray(); var mockImportOptions = new ImportBibleNotesParams(); - int mepsLanguageId = 0; + const int mepsLanguageId = 0; + var importer = new NotesImporter( file1.Database, "nwtsty", @@ -41,17 +42,17 @@ Assert.IsTrue(result.BibleNotesUpdated == 0); } - private IEnumerable CreateMockBibleNotes() + private static IEnumerable CreateMockBibleNotes() { return new List { - new BibleNote + new() { BookChapterAndVerse = new BibleBookChapterAndVerse(1, 1, 1), NoteTitle = "A note 1", NoteContent = "My notes go here", }, - new BibleNote + new() { BookChapterAndVerse = new BibleBookChapterAndVerse(2, 2, 2), NoteTitle = "A note 2", diff --git a/JWLMerge.Tests/TestMerge.cs b/JWLMerge.Tests/TestMerge.cs index 2eb99f5..f99829c 100644 --- a/JWLMerge.Tests/TestMerge.cs +++ b/JWLMerge.Tests/TestMerge.cs @@ -1,7 +1,6 @@ namespace JWLMerge.Tests { - using System; - using JWLMerge.BackupFileServices.Helpers; + using BackupFileServices.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -10,13 +9,13 @@ [TestMethod] public void TestMerge1() { - int numRecords = 100; + const int numRecords = 100; var file1 = CreateMockBackup(numRecords); var file2 = CreateMockBackup(numRecords); var file3 = CreateMockBackup(numRecords); - Merger merger = new Merger(); + var merger = new Merger(); var mergedDatabase = merger.Merge(new[] { file1.Database, file2.Database, file3.Database }); mergedDatabase.CheckValidity(); diff --git a/JWLMerge.sln b/JWLMerge.sln index 95084b0..b073070 100644 --- a/JWLMerge.sln +++ b/JWLMerge.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWLMerge.BackupFileServices", "JWLMerge.BackupFileServices\JWLMerge.BackupFileServices.csproj", "{83446629-CDBB-43FF-B628-1B8A3A9603C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWLMerge.BackupFileServices", "JWLMerge.BackupFileServices\JWLMerge.BackupFileServices.csproj", "{83446629-CDBB-43FF-B628-1B8A3A9603C3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JWL Database Schemas", "JWL Database Schemas", "{DA7BDF58-CFEA-489C-B18C-944D9986758D}" ProjectSection(SolutionItems) = preProject @@ -13,21 +13,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JWL Database Schemas", "JWL Version008.txt = Version008.txt EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWLMergeCLI", "JWLMergeCLI\JWLMergeCLI.csproj", "{2CBBA1C2-72C9-4287-A262-EC1D2A2F6E56}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWLMergeCLI", "JWLMergeCLI\JWLMergeCLI.csproj", "{2CBBA1C2-72C9-4287-A262-EC1D2A2F6E56}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA506BB2-675E-47BE-BC54-ED6EAE369243}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - JWLMerge\JWLMerge.ruleset = JWLMerge\JWLMerge.ruleset jwlmergecmdline.png = jwlmergecmdline.png SolutionInfo.cs = SolutionInfo.cs EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWLMerge.Tests", "JWLMerge.Tests\JWLMerge.Tests.csproj", "{07B843A0-2DD7-40C1-AFE9-5F1BCA22393E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWLMerge.Tests", "JWLMerge.Tests\JWLMerge.Tests.csproj", "{07B843A0-2DD7-40C1-AFE9-5F1BCA22393E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWLMerge", "JWLMerge\JWLMerge.csproj", "{0D88A466-7C90-4C0B-B8BA-E1F5EC91D4A4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWLMerge", "JWLMerge\JWLMerge.csproj", "{0D88A466-7C90-4C0B-B8BA-E1F5EC91D4A4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JWLMerge.ExcelServices", "JWLMerge.ExcelServices\JWLMerge.ExcelServices.csproj", "{F8D64B6C-C475-4B5E-8B32-D717380F0F36}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JWLMerge.ExcelServices", "JWLMerge.ExcelServices\JWLMerge.ExcelServices.csproj", "{F8D64B6C-C475-4B5E-8B32-D717380F0F36}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/JWLMerge.sln.DotSettings b/JWLMerge.sln.DotSettings index 6102315..0c850ad 100644 --- a/JWLMerge.sln.DotSettings +++ b/JWLMerge.sln.DotSettings @@ -1,7 +1,9 @@  True + True True True + True True True True @@ -10,4 +12,6 @@ True True True + True + True True \ No newline at end of file diff --git a/JWLMerge/App.xaml.cs b/JWLMerge/App.xaml.cs index e06513f..76becaf 100644 --- a/JWLMerge/App.xaml.cs +++ b/JWLMerge/App.xaml.cs @@ -10,10 +10,10 @@ namespace JWLMerge using System.Windows.Interop; using System.Windows.Media; using Serilog; - using JWLMerge.BackupFileServices; - using JWLMerge.ExcelServices; - using JWLMerge.Services; - using JWLMerge.ViewModel; + using BackupFileServices; + using ExcelServices; + using Services; + using ViewModel; using Microsoft.Extensions.DependencyInjection; using Microsoft.Toolkit.Mvvm.DependencyInjection; @@ -23,15 +23,14 @@ namespace JWLMerge public partial class App : Application { private readonly string _appString = "JWLMergeAC"; - private Mutex _appMutex; + private Mutex? _appMutex; protected override void OnExit(ExitEventArgs e) { _appMutex?.Dispose(); Log.Logger.Information("==== Exit ===="); } - - + protected override void OnStartup(StartupEventArgs e) { ConfigureServices(); @@ -60,7 +59,7 @@ namespace JWLMerge Current.Shutdown(); } - private void ConfigureServices() + private static void ConfigureServices() { var serviceCollection = new ServiceCollection(); @@ -88,7 +87,7 @@ namespace JWLMerge Ioc.Default.ConfigureServices(serviceProvider); } - private bool ForceSoftwareRendering() + private static bool ForceSoftwareRendering() { // https://blogs.msdn.microsoft.com/jgoldb/2010/06/22/software-rendering-usage-in-wpf/ // renderingTier values: diff --git a/JWLMerge/Helpers/ColourHelper.cs b/JWLMerge/Helpers/ColourHelper.cs index c340280..71302f1 100644 --- a/JWLMerge/Helpers/ColourHelper.cs +++ b/JWLMerge/Helpers/ColourHelper.cs @@ -1,10 +1,9 @@ namespace JWLMerge.Helpers { - using System; using System.Collections.Generic; using System.Linq; using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.Models; + using Models; internal static class ColourHelper { diff --git a/JWLMerge/Helpers/DesignTimeFileCreation.cs b/JWLMerge/Helpers/DesignTimeFileCreation.cs index 92937e0..9e2061a 100644 --- a/JWLMerge/Helpers/DesignTimeFileCreation.cs +++ b/JWLMerge/Helpers/DesignTimeFileCreation.cs @@ -1,9 +1,9 @@ namespace JWLMerge.Helpers { using System; - using JWLMerge.BackupFileServices; + using BackupFileServices; using JWLMerge.BackupFileServices.Models.ManifestFile; - using JWLMerge.Models; + using Models; internal static class DesignTimeFileCreation { @@ -20,11 +20,9 @@ DeviceName = "MYPC", }; - return new JwLibraryFile - { - FilePath = "c:\\temp\\myfile.jwlibrary", - BackupFile = file, - }; +#pragma warning disable S1075 // URIs should not be hardcoded + return new JwLibraryFile("c:\\temp\\myfile.jwlibrary", file); +#pragma warning restore S1075 // URIs should not be hardcoded } private static string GenerateDateString(DateTime dateTime) diff --git a/JWLMerge/Helpers/MergePreparation.cs b/JWLMerge/Helpers/MergePreparation.cs index 021c25b..69270f6 100644 --- a/JWLMerge/Helpers/MergePreparation.cs +++ b/JWLMerge/Helpers/MergePreparation.cs @@ -1,8 +1,8 @@ namespace JWLMerge.Helpers { - using JWLMerge.BackupFileServices; + using BackupFileServices; using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.Models; + using Models; internal static class MergePreparation { diff --git a/JWLMerge/Helpers/PublicationHelper.cs b/JWLMerge/Helpers/PublicationHelper.cs index 43d4c89..e70d4ee 100644 --- a/JWLMerge/Helpers/PublicationHelper.cs +++ b/JWLMerge/Helpers/PublicationHelper.cs @@ -3,29 +3,24 @@ using System.Collections.Generic; using System.Linq; using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.Models; + using Models; internal static class PublicationHelper { - public static PublicationDef[] GetPublications(List locations, List userMarks, bool includeAllPublicationsItem) + public static PublicationDef[] GetPublications( + List locations, List userMarks, bool includeAllPublicationsItem) { var locationsThatAreMarked = userMarks.Select(x => x.LocationId).ToHashSet(); var result = locations - .Where(x => locationsThatAreMarked.Contains(x.LocationId)) - .Select(x => x.KeySymbol).Distinct().Select( - x => new PublicationDef - { - KeySymbol = x, - }).OrderBy(x => x.KeySymbol).ToList(); + .Where(x => locationsThatAreMarked.Contains(x.LocationId) && !string.IsNullOrEmpty(x.KeySymbol)) + .Select(x => x.KeySymbol).Distinct() + .Select(x => new PublicationDef(x!, false)) + .OrderBy(x => x.KeySymbol).ToList(); if (includeAllPublicationsItem) { - result.Insert(0, new PublicationDef - { - IsAllPublicationsSymbol = true, - KeySymbol = "All Publications", - }); + result.Insert(0, new PublicationDef("All Publications", true)); } return result.ToArray(); diff --git a/JWLMerge/Helpers/VersionDetection.cs b/JWLMerge/Helpers/VersionDetection.cs index a23fe73..aa933f1 100644 --- a/JWLMerge/Helpers/VersionDetection.cs +++ b/JWLMerge/Helpers/VersionDetection.cs @@ -8,25 +8,26 @@ internal static class VersionDetection { - public static Version GetLatestReleaseVersion(string latestReleaseUrl) + public static Version? GetLatestReleaseVersion(string latestReleaseUrl) { - string version = null; + string? version = null; try { - using (var client = new HttpClient()) +#pragma warning disable U2U1025 // Avoid instantiating HttpClient + using var client = new HttpClient(); +#pragma warning restore U2U1025 // Avoid instantiating HttpClient + + var response = client.GetAsync(new Uri(latestReleaseUrl)).Result; + if (response.IsSuccessStatusCode) { - var response = client.GetAsync(new Uri(latestReleaseUrl)).Result; - if (response.IsSuccessStatusCode) + var latestVersionUri = response.RequestMessage?.RequestUri; + if (latestVersionUri != null) { - var latestVersionUri = response.RequestMessage.RequestUri; - if (latestVersionUri != null) + var segments = latestVersionUri.Segments; + if (segments.Any()) { - var segments = latestVersionUri.Segments; - if (segments.Any()) - { - version = segments[segments.Length - 1]; - } + version = segments[segments.Length - 1]; } } } @@ -42,7 +43,9 @@ public static Version GetCurrentVersion() { var ver = Assembly.GetExecutingAssembly().GetName().Version; - return new Version($"{ver.Major}.{ver.Minor}.{ver.Build}.{ver.Revision}"); + return ver != null + ? new Version($"{ver.Major}.{ver.Minor}.{ver.Build}.{ver.Revision}") + : new Version(); } } } diff --git a/JWLMerge/JWLMerge.csproj b/JWLMerge/JWLMerge.csproj index cd803ba..1270149 100644 --- a/JWLMerge/JWLMerge.csproj +++ b/JWLMerge/JWLMerge.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/JWLMerge/MainWindow.xaml.cs b/JWLMerge/MainWindow.xaml.cs index 529cc32..9e5c108 100644 --- a/JWLMerge/MainWindow.xaml.cs +++ b/JWLMerge/MainWindow.xaml.cs @@ -2,7 +2,7 @@ { using System.ComponentModel; using System.Windows; - using JWLMerge.Messages; + using Messages; using Microsoft.Toolkit.Mvvm.Messaging; /// @@ -17,17 +17,17 @@ private void PanelOnDragOver(object sender, DragEventArgs e) { - WeakReferenceMessenger.Default.Send(new DragOverMessage { DragEventArgs = e }); + WeakReferenceMessenger.Default.Send(new DragOverMessage(e)); } private void PanelOnDrop(object sender, DragEventArgs e) { - WeakReferenceMessenger.Default.Send(new DragDropMessage { DragEventArgs = e }); + WeakReferenceMessenger.Default.Send(new DragDropMessage(e)); } private void MainWindowOnClosing(object sender, CancelEventArgs e) { - WeakReferenceMessenger.Default.Send(new MainWindowClosingMessage { CancelEventArgs = e }); + WeakReferenceMessenger.Default.Send(new MainWindowClosingMessage(e)); } } } diff --git a/JWLMerge/Messages/DragDropMessage.cs b/JWLMerge/Messages/DragDropMessage.cs index 335b3ff..9173378 100644 --- a/JWLMerge/Messages/DragDropMessage.cs +++ b/JWLMerge/Messages/DragDropMessage.cs @@ -2,8 +2,13 @@ { using System.Windows; - internal class DragDropMessage + internal sealed class DragDropMessage { - public DragEventArgs DragEventArgs { get; set; } + public DragDropMessage(DragEventArgs args) + { + DragEventArgs = args; + } + + public DragEventArgs DragEventArgs { get; } } } diff --git a/JWLMerge/Messages/DragOverMessage.cs b/JWLMerge/Messages/DragOverMessage.cs index e2bd59f..3099a0d 100644 --- a/JWLMerge/Messages/DragOverMessage.cs +++ b/JWLMerge/Messages/DragOverMessage.cs @@ -2,8 +2,13 @@ { using System.Windows; - internal class DragOverMessage + internal sealed class DragOverMessage { - public DragEventArgs DragEventArgs { get; set; } + public DragOverMessage(DragEventArgs args) + { + DragEventArgs = args; + } + + public DragEventArgs DragEventArgs { get; } } } diff --git a/JWLMerge/Messages/MainWindowClosingMessage.cs b/JWLMerge/Messages/MainWindowClosingMessage.cs index 82ba028..f81eac2 100644 --- a/JWLMerge/Messages/MainWindowClosingMessage.cs +++ b/JWLMerge/Messages/MainWindowClosingMessage.cs @@ -2,8 +2,13 @@ { using System.ComponentModel; - internal class MainWindowClosingMessage + internal sealed class MainWindowClosingMessage { - public CancelEventArgs CancelEventArgs { get; set; } + public MainWindowClosingMessage(CancelEventArgs args) + { + CancelEventArgs = args; + } + + public CancelEventArgs CancelEventArgs { get; } } } diff --git a/JWLMerge/Models/ColorResult.cs b/JWLMerge/Models/ColorResult.cs index 736e1fc..994cf02 100644 --- a/JWLMerge/Models/ColorResult.cs +++ b/JWLMerge/Models/ColorResult.cs @@ -1,8 +1,8 @@ namespace JWLMerge.Models { - internal class ColorResult + internal sealed class ColorResult { - public int[] ColourIndexes { get; set; } + public int[]? ColourIndexes { get; set; } public bool RemoveNotes { get; set; } } diff --git a/JWLMerge/Models/ColourDef.cs b/JWLMerge/Models/ColourDef.cs index 05a9e1e..4977274 100644 --- a/JWLMerge/Models/ColourDef.cs +++ b/JWLMerge/Models/ColourDef.cs @@ -2,7 +2,7 @@ { using System.Windows.Media; - internal class ColourDef + internal sealed class ColourDef { public ColourDef(int colourIndex, string name, string rgb) { @@ -11,11 +11,11 @@ RgbString = rgb; } - public int ColourIndex { get; set; } + public int ColourIndex { get; } - public string Name { get; set; } + public string Name { get; } - public string RgbString { get; set; } + public string RgbString { get; } public Color Color => (Color)ColorConverter.ConvertFromString(RgbString); } diff --git a/JWLMerge/Models/ColourListItem.cs b/JWLMerge/Models/ColourListItem.cs index bb7a899..0a9549c 100644 --- a/JWLMerge/Models/ColourListItem.cs +++ b/JWLMerge/Models/ColourListItem.cs @@ -3,15 +3,22 @@ using Microsoft.Toolkit.Mvvm.ComponentModel; using System.Windows.Media; - internal class ColourListItem : ObservableObject + internal sealed class ColourListItem : ObservableObject { private bool _isChecked; - public string Name { get; set; } + public ColourListItem(string name, int id, Color color) + { + Name = name; + Id = id; + Color = color; + } - public int Id { get; set; } + public string Name { get; } - public Color Color { get; set; } + public int Id { get; } + + public Color Color { get; } public bool IsChecked { diff --git a/JWLMerge/Models/DataTypeListItem.cs b/JWLMerge/Models/DataTypeListItem.cs index da04b5b..3a8e84f 100644 --- a/JWLMerge/Models/DataTypeListItem.cs +++ b/JWLMerge/Models/DataTypeListItem.cs @@ -1,10 +1,15 @@ namespace JWLMerge.Models { - internal class DataTypeListItem + internal sealed class DataTypeListItem { - // ReSharper disable once UnusedAutoPropertyAccessor.Global - public string Caption { get; set; } + public DataTypeListItem(string caption, JwLibraryFileDataTypes dataType) + { + Caption = caption; + DataType = dataType; + } - public JwLibraryFileDataTypes DataType { get; set; } + public string Caption { get; } + + public JwLibraryFileDataTypes DataType { get; } } } diff --git a/JWLMerge/Models/FileFormatErrorListItem.cs b/JWLMerge/Models/FileFormatErrorListItem.cs index 9fcfceb..472334c 100644 --- a/JWLMerge/Models/FileFormatErrorListItem.cs +++ b/JWLMerge/Models/FileFormatErrorListItem.cs @@ -1,9 +1,15 @@ namespace JWLMerge.Models { - internal class FileFormatErrorListItem + internal sealed class FileFormatErrorListItem { - public string Filename { get; set; } + public FileFormatErrorListItem(string filename, string errorMsg) + { + Filename = filename; + ErrorMsg = errorMsg; + } - public string ErrorMsg { get; set; } + public string Filename { get; } + + public string ErrorMsg { get; } } } diff --git a/JWLMerge/Models/JwLibraryFile.cs b/JWLMerge/Models/JwLibraryFile.cs index 2125565..adf425f 100644 --- a/JWLMerge/Models/JwLibraryFile.cs +++ b/JWLMerge/Models/JwLibraryFile.cs @@ -1,19 +1,25 @@ -namespace JWLMerge.Models +using System.Diagnostics.CodeAnalysis; + +namespace JWLMerge.Models { using System.Text; using JWLMerge.BackupFileServices.Models; using Microsoft.Toolkit.Mvvm.ComponentModel; - internal class JwLibraryFile : ObservableObject + internal sealed class JwLibraryFile : ObservableObject { - public JwLibraryFile() + public JwLibraryFile(string filePath, BackupFile backupFile) { + FilePath = filePath; + BackupFile = backupFile; + MergeParameters = new MergeParameters(); MergeParameters.PropertyChanged += MergeParametersPropertyChanged; } - public string FilePath { get; set; } + public string FilePath { get; } + [DisallowNull] public BackupFile BackupFile { get; set; } public MergeParameters MergeParameters { get; } @@ -40,7 +46,7 @@ OnPropertyChanged(nameof(TooltipSummaryText)); } - private void MergeParametersPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void MergeParametersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { OnPropertyChanged(nameof(MergeParameters)); } diff --git a/JWLMerge/Models/PubColourResult.cs b/JWLMerge/Models/PubColourResult.cs index 93eb315..9085ca1 100644 --- a/JWLMerge/Models/PubColourResult.cs +++ b/JWLMerge/Models/PubColourResult.cs @@ -1,8 +1,8 @@ namespace JWLMerge.Models { - internal class PubColourResult + internal sealed class PubColourResult { - public string PublicationSymbol { get; set; } + public string? PublicationSymbol { get; set; } public int ColorIndex { get; set; } diff --git a/JWLMerge/Models/PublicationDef.cs b/JWLMerge/Models/PublicationDef.cs index 554571d..2006f03 100644 --- a/JWLMerge/Models/PublicationDef.cs +++ b/JWLMerge/Models/PublicationDef.cs @@ -1,9 +1,15 @@ namespace JWLMerge.Models { - internal class PublicationDef + internal sealed class PublicationDef { - public string KeySymbol { get; set; } + public PublicationDef(string keySymbol, bool isAllPubSymbol) + { + KeySymbol = keySymbol; + IsAllPublicationsSymbol = isAllPubSymbol; + } - public bool IsAllPublicationsSymbol { get; set; } + public string KeySymbol { get; } + + public bool IsAllPublicationsSymbol { get; } } } diff --git a/JWLMerge/Models/TagListItem.cs b/JWLMerge/Models/TagListItem.cs index b467637..52cb5b0 100644 --- a/JWLMerge/Models/TagListItem.cs +++ b/JWLMerge/Models/TagListItem.cs @@ -2,13 +2,19 @@ { using Microsoft.Toolkit.Mvvm.ComponentModel; - internal class TagListItem : ObservableObject + internal sealed class TagListItem : ObservableObject { private bool _isChecked; - public string Name { get; set; } + public TagListItem(string name, int id) + { + Name = name; + Id = id; + } - public int Id { get; set; } + public string Name { get; } + + public int Id { get; } public bool IsChecked { diff --git a/JWLMerge/Services/DialogService.cs b/JWLMerge/Services/DialogService.cs index f346edb..ab45f89 100644 --- a/JWLMerge/Services/DialogService.cs +++ b/JWLMerge/Services/DialogService.cs @@ -4,16 +4,16 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using JWLMerge.BackupFileServices.Exceptions; + using BackupFileServices.Exceptions; using JWLMerge.BackupFileServices.Models; using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.Dialogs; - using JWLMerge.Models; - using JWLMerge.ViewModel; + using Dialogs; + using Models; + using ViewModel; using MaterialDesignThemes.Wpf; // ReSharper disable once ClassNeverInstantiated.Global - internal class DialogService : IDialogService + internal sealed class DialogService : IDialogService { public const int UntaggedItemId = -1; private const string NotTaggedString = "** Not Tagged **"; @@ -36,16 +36,13 @@ switch (e) { case WrongDatabaseVersionException dbVerEx: - dc.Errors.Add(new FileFormatErrorListItem - { Filename = dbVerEx.Filename, ErrorMsg = dbVerEx.Message }); + dc.Errors.Add(new FileFormatErrorListItem(dbVerEx.Filename ?? "Error", dbVerEx.Message)); break; case WrongManifestVersionException mftVerEx: - dc.Errors.Add(new FileFormatErrorListItem - { Filename = mftVerEx.Filename, ErrorMsg = mftVerEx.Message }); + dc.Errors.Add(new FileFormatErrorListItem(mftVerEx.Filename ?? "Error", mftVerEx.Message)); break; default: - dc.Errors.Add(new FileFormatErrorListItem - { Filename = "Error", ErrorMsg = bex.Message }); + dc.Errors.Add(new FileFormatErrorListItem("Error", bex.Message)); break; } } @@ -53,10 +50,7 @@ await DialogHost.Show( dialog, - (object sender, DialogClosingEventArgs args) => - { - _isDialogVisible = false; - }).ConfigureAwait(false); + (object sender, DialogClosingEventArgs args) => _isDialogVisible = false).ConfigureAwait(false); } public async Task ShouldRemoveFavouritesAsync() @@ -102,20 +96,12 @@ if (includeUntaggedNotes) { - dc.TagItems.Add(new TagListItem - { - Id = UntaggedItemId, - Name = NotTaggedString, - }); + dc.TagItems.Add(new TagListItem(NotTaggedString, UntaggedItemId)); } foreach (var tag in tags) { - dc.TagItems.Add(new TagListItem - { - Id = tag.TagId, - Name = tag.Name, - }); + dc.TagItems.Add(new TagListItem(tag.Name, tag.TagId)); } await DialogHost.Show( @@ -147,12 +133,7 @@ foreach (var c in colours) { - dc.ColourItems.Add(new ColourListItem - { - Id = c.ColourIndex, - Name = c.Name, - Color = c.Color, - }); + dc.ColourItems.Add(new ColourListItem(c.Name, c.ColourIndex, c.Color)); } await DialogHost.Show( @@ -167,7 +148,7 @@ }; } - public async Task GetPubAndColourSelectionForUnderlineRemovalAsync(PublicationDef[] pubs, ColourDef[] colors) + public async Task GetPubAndColourSelectionForUnderlineRemovalAsync(PublicationDef[] pubs, ColourDef[] colors) { _isDialogVisible = true; @@ -180,12 +161,7 @@ foreach (var c in colors) { - dc.ColourItems.Add(new ColourListItem - { - Id = c.ColourIndex, - Name = c.Name, - Color = c.Color, - }); + dc.ColourItems.Add(new ColourListItem(c.Name, c.ColourIndex, c.Color)); } foreach (var p in pubs) @@ -201,7 +177,7 @@ return dc.Result; } - public async Task GetImportBibleNotesParamsAsync(IReadOnlyCollection databaseTags) + public async Task GetImportBibleNotesParamsAsync(IReadOnlyCollection databaseTags) { _isDialogVisible = true; diff --git a/JWLMerge/Services/DragDropService.cs b/JWLMerge/Services/DragDropService.cs index e2f452e..2a9560c 100644 --- a/JWLMerge/Services/DragDropService.cs +++ b/JWLMerge/Services/DragDropService.cs @@ -6,7 +6,7 @@ using System.Windows; // ReSharper disable once ClassNeverInstantiated.Global - internal class DragDropService : IDragDropService + internal sealed class DragDropService : IDragDropService { /// /// Determines whether we can accept the drag and drop operation @@ -42,7 +42,7 @@ { foreach (var filePath in dataObject.GetFileDropList()) { - if (IsJwLibraryFile(filePath)) + if (IsJwLibraryFile(filePath) && !string.IsNullOrEmpty(filePath)) { result.Add(filePath); } @@ -52,10 +52,15 @@ return result; } - private bool IsJwLibraryFile(string filePath) + private static bool IsJwLibraryFile(string? filePath) { + if (string.IsNullOrEmpty(filePath)) + { + return false; + } + var ext = Path.GetExtension(filePath); - return ext != null && ext.Equals(".jwlibrary", StringComparison.OrdinalIgnoreCase); + return ext.Equals(".jwlibrary", StringComparison.OrdinalIgnoreCase); } } } diff --git a/JWLMerge/Services/FileOpenSaveService.cs b/JWLMerge/Services/FileOpenSaveService.cs index 9524f7b..281ca1b 100644 --- a/JWLMerge/Services/FileOpenSaveService.cs +++ b/JWLMerge/Services/FileOpenSaveService.cs @@ -5,15 +5,15 @@ using Microsoft.Win32; // ReSharper disable once ClassNeverInstantiated.Global - internal class FileOpenSaveService : IFileOpenSaveService + internal sealed class FileOpenSaveService : IFileOpenSaveService { - private static string SaveDirectory { get; set; } + private static string? SaveDirectory { get; set; } - private static string ImportDirectory { get; set; } + private static string? ImportDirectory { get; set; } - private static string ExportDirectory { get; set; } + private static string? ExportDirectory { get; set; } - public string GetBibleNotesExportFilePath(string title) + public string? GetBibleNotesExportFilePath(string title) { var saveFileDialog = new SaveFileDialog { @@ -32,7 +32,7 @@ return null; } - public string GetBibleNotesImportFilePath(string title) + public string? GetBibleNotesImportFilePath(string title) { var openFileDialog = new OpenFileDialog { @@ -53,7 +53,7 @@ return null; } - public string GetSaveFilePath(string title) + public string? GetSaveFilePath(string title) { var saveFileDialog = new SaveFileDialog { @@ -72,7 +72,7 @@ return null; } - private string GetJWLMergeDocsFolder() + private static string GetJWLMergeDocsFolder() { var folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "JWLMerge"); if (!Directory.Exists(folder)) @@ -83,17 +83,17 @@ return folder; } - private string GetDefaultSaveFolder() + private static string GetDefaultSaveFolder() { return GetJWLMergeDocsFolder(); } - private string GetDefaultExportFolder() + private static string GetDefaultExportFolder() { return GetJWLMergeDocsFolder(); } - private string GetDefaultImportFolder() + private static string GetDefaultImportFolder() { var folder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); if (!Directory.Exists(folder)) diff --git a/JWLMerge/Services/IDialogService.cs b/JWLMerge/Services/IDialogService.cs index bcd5c80..39705f6 100644 --- a/JWLMerge/Services/IDialogService.cs +++ b/JWLMerge/Services/IDialogService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using JWLMerge.BackupFileServices.Models; using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.Models; + using Models; internal interface IDialogService { @@ -15,13 +15,13 @@ Task ShouldRemoveFavouritesAsync(); - Task GetImportBibleNotesParamsAsync(IReadOnlyCollection databaseTags); + Task GetImportBibleNotesParamsAsync(IReadOnlyCollection databaseTags); Task GetTagSelectionForNotesRemovalAsync(Tag[] tags, bool includeUntaggedNotes); Task GetColourSelectionForUnderlineRemovalAsync(ColourDef[] colours); - Task GetPubAndColourSelectionForUnderlineRemovalAsync(PublicationDef[] pubs, ColourDef[] colors); + Task GetPubAndColourSelectionForUnderlineRemovalAsync(PublicationDef[] pubs, ColourDef[] colors); bool IsDialogVisible(); } diff --git a/JWLMerge/Services/IFileOpenSaveService.cs b/JWLMerge/Services/IFileOpenSaveService.cs index 292eebb..52063d0 100644 --- a/JWLMerge/Services/IFileOpenSaveService.cs +++ b/JWLMerge/Services/IFileOpenSaveService.cs @@ -2,10 +2,10 @@ { internal interface IFileOpenSaveService { - string GetSaveFilePath(string title); + string? GetSaveFilePath(string title); - string GetBibleNotesImportFilePath(string title); + string? GetBibleNotesImportFilePath(string title); - string GetBibleNotesExportFilePath(string title); + string? GetBibleNotesExportFilePath(string title); } } diff --git a/JWLMerge/Services/ISnackbarService.cs b/JWLMerge/Services/ISnackbarService.cs index cc60448..6a75b2d 100644 --- a/JWLMerge/Services/ISnackbarService.cs +++ b/JWLMerge/Services/ISnackbarService.cs @@ -12,7 +12,7 @@ void Enqueue( object content, object actionContent, - Action actionHandler, + Action actionHandler, object actionArgument, bool promote, bool neverConsiderToBeDuplicate); diff --git a/JWLMerge/Services/NotesByTagResult.cs b/JWLMerge/Services/NotesByTagResult.cs index 318c85a..227432c 100644 --- a/JWLMerge/Services/NotesByTagResult.cs +++ b/JWLMerge/Services/NotesByTagResult.cs @@ -1,8 +1,8 @@ namespace JWLMerge.Services { - internal class NotesByTagResult + internal sealed class NotesByTagResult { - public int[] TagIds { get; set; } + public int[]? TagIds { get; set; } public bool RemoveUntaggedNotes { get; set; } diff --git a/JWLMerge/Services/SnackbarService.cs b/JWLMerge/Services/SnackbarService.cs index 2ab885d..2261140 100644 --- a/JWLMerge/Services/SnackbarService.cs +++ b/JWLMerge/Services/SnackbarService.cs @@ -16,7 +16,7 @@ public void Enqueue( object content, object actionContent, - Action actionHandler, + Action actionHandler, object actionArgument, bool promote, bool neverConsiderToBeDuplicate) @@ -54,7 +54,7 @@ public void Dispose() { - ((SnackbarMessageQueue)TheSnackbarMessageQueue)?.Dispose(); + ((SnackbarMessageQueue)TheSnackbarMessageQueue).Dispose(); } } } diff --git a/JWLMerge/Services/WindowService.cs b/JWLMerge/Services/WindowService.cs index 0c3e49d..dee2a9e 100644 --- a/JWLMerge/Services/WindowService.cs +++ b/JWLMerge/Services/WindowService.cs @@ -4,11 +4,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; - using JWLMerge.BackupFileServices; - using JWLMerge.ViewModel; + using BackupFileServices; + using ViewModel; // ReSharper disable once ClassNeverInstantiated.Global - internal class WindowService : IWindowService + internal sealed class WindowService : IWindowService { private readonly List _detailWindows; @@ -67,9 +67,9 @@ return window; } - private void DetailWindowClosed(object sender, EventArgs e) + private void DetailWindowClosed(object? sender, EventArgs e) { - var window = (DetailWindow)sender; + var window = (DetailWindow?)sender; if (window != null) { _detailWindows.Remove(window); @@ -77,12 +77,17 @@ } } - private DetailWindow GetDetailWindow(string filePath) + private DetailWindow? GetDetailWindow(string filePath) { + if (string.IsNullOrEmpty(filePath)) + { + return null; + } + foreach (var window in _detailWindows) { var path = ((DetailViewModel)window.DataContext).FilePath; - if (IsSameFile(path, filePath)) + if (!string.IsNullOrEmpty(path) && IsSameFile(path, filePath)) { return window; } @@ -91,7 +96,7 @@ return null; } - private bool IsSameFile(string path1, string path2) + private static bool IsSameFile(string path1, string path2) { return Path.GetFullPath(path1).Equals(Path.GetFullPath(path2), StringComparison.OrdinalIgnoreCase); } diff --git a/JWLMerge/ViewModel/BackupFileFormatErrorViewModel.cs b/JWLMerge/ViewModel/BackupFileFormatErrorViewModel.cs index 7e62481..f4676ae 100644 --- a/JWLMerge/ViewModel/BackupFileFormatErrorViewModel.cs +++ b/JWLMerge/ViewModel/BackupFileFormatErrorViewModel.cs @@ -1,19 +1,19 @@ namespace JWLMerge.ViewModel { using System.Collections.Generic; - using JWLMerge.Models; + using Models; using MaterialDesignThemes.Wpf; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; - internal class BackupFileFormatErrorViewModel : ObservableObject + internal sealed class BackupFileFormatErrorViewModel : ObservableObject { public BackupFileFormatErrorViewModel() { OkCommand = new RelayCommand(Ok); } - public List Errors { get; } = new List(); + public List Errors { get; } = new(); public RelayCommand OkCommand { get; } diff --git a/JWLMerge/ViewModel/DetailViewModel.cs b/JWLMerge/ViewModel/DetailViewModel.cs index cd7325c..6c365b8 100644 --- a/JWLMerge/ViewModel/DetailViewModel.cs +++ b/JWLMerge/ViewModel/DetailViewModel.cs @@ -5,25 +5,25 @@ using JWLMerge.BackupFileServices.Models; using JWLMerge.BackupFileServices.Models.DatabaseModels; using JWLMerge.BackupFileServices.Models.ManifestFile; - using JWLMerge.Models; + using Models; using Microsoft.Toolkit.Mvvm.ComponentModel; // ReSharper disable once ClassNeverInstantiated.Global - internal class DetailViewModel : ObservableObject + internal sealed class DetailViewModel : ObservableObject { - private DataTypeListItem _selectedDataType; + private DataTypeListItem? _selectedDataType; private bool _notesRedacted; - private string _windowTitle; - private BackupFile _backupFile; + private string? _windowTitle; + private BackupFile? _backupFile; public DetailViewModel() { ListItems = CreateListItems(); } - public string FilePath { get; set; } + public string? FilePath { get; set; } - public BackupFile BackupFile + public BackupFile? BackupFile { get => _backupFile; set @@ -36,13 +36,13 @@ } } } - + public List ListItems { get; } public string WindowTitle { get => _windowTitle ?? string.Empty; - set => SetProperty(ref _windowTitle, value); + private set => SetProperty(ref _windowTitle, value); } public bool NotesRedacted @@ -61,7 +61,7 @@ public bool NotesNotRedacted => !NotesRedacted; - public DataTypeListItem SelectedDataType + public DataTypeListItem? SelectedDataType { get => _selectedDataType; set @@ -76,7 +76,9 @@ } } - public IEnumerable DataItemsSource +#pragma warning disable U2U1200 // Prefer generic collections over non-generic ones + public IEnumerable? DataItemsSource +#pragma warning restore U2U1200 // Prefer generic collections over non-generic ones { get { @@ -118,13 +120,17 @@ } } +#pragma warning disable S1168 // Empty arrays and collections should be returned instead of null return null; +#pragma warning restore S1168 // Empty arrays and collections should be returned instead of null } } - public bool IsNotesItemSelected => SelectedDataType.DataType == JwLibraryFileDataTypes.Note; - - private IEnumerable ManifestAsItemsSource(Manifest manifest) + public bool IsNotesItemSelected => SelectedDataType?.DataType == JwLibraryFileDataTypes.Note; + +#pragma warning disable U2U1011 // Return types should be specific + private static IEnumerable ManifestAsItemsSource(Manifest? manifest) +#pragma warning restore U2U1011 // Return types should be specific { var result = new List>(); @@ -144,20 +150,20 @@ return result; } - private List CreateListItems() + private static List CreateListItems() { - return new List + return new() { - new DataTypeListItem { Caption = "Manifest", DataType = JwLibraryFileDataTypes.Manifest }, - new DataTypeListItem { Caption = "Block Range", DataType = JwLibraryFileDataTypes.BlockRange }, - new DataTypeListItem { Caption = "Bookmark", DataType = JwLibraryFileDataTypes.Bookmark }, - new DataTypeListItem { Caption = "InputField", DataType = JwLibraryFileDataTypes.InputField }, - new DataTypeListItem { Caption = "Last Modified", DataType = JwLibraryFileDataTypes.LastModified }, - new DataTypeListItem { Caption = "Location", DataType = JwLibraryFileDataTypes.Location }, - new DataTypeListItem { Caption = "Note", DataType = JwLibraryFileDataTypes.Note }, - new DataTypeListItem { Caption = "Tag", DataType = JwLibraryFileDataTypes.Tag }, - new DataTypeListItem { Caption = "Tag Map", DataType = JwLibraryFileDataTypes.TagMap }, - new DataTypeListItem { Caption = "User Mark", DataType = JwLibraryFileDataTypes.UserMark }, + new("Manifest", JwLibraryFileDataTypes.Manifest), + new("Block Range", JwLibraryFileDataTypes.BlockRange), + new("Bookmark", JwLibraryFileDataTypes.Bookmark), + new("InputField", JwLibraryFileDataTypes.InputField), + new("Last Modified", JwLibraryFileDataTypes.LastModified), + new("Location", JwLibraryFileDataTypes.Location), + new("Note", JwLibraryFileDataTypes.Note), + new("Tag", JwLibraryFileDataTypes.Tag), + new("Tag Map", JwLibraryFileDataTypes.TagMap), + new("User Mark", JwLibraryFileDataTypes.UserMark), }; } } diff --git a/JWLMerge/ViewModel/ImportBibleNotesViewModel.cs b/JWLMerge/ViewModel/ImportBibleNotesViewModel.cs index 748f6e4..7cd27c4 100644 --- a/JWLMerge/ViewModel/ImportBibleNotesViewModel.cs +++ b/JWLMerge/ViewModel/ImportBibleNotesViewModel.cs @@ -8,9 +8,9 @@ using Microsoft.Toolkit.Mvvm.Input; // ReSharper disable once ClassNeverInstantiated.Global - internal class ImportBibleNotesViewModel : ObservableObject + internal sealed class ImportBibleNotesViewModel : ObservableObject { - private IReadOnlyCollection _tags; + private IReadOnlyCollection? _tags; public ImportBibleNotesViewModel() { @@ -18,13 +18,13 @@ CancelCommand = new RelayCommand(Cancel); } - public ImportBibleNotesParams Result { get; private set; } + public ImportBibleNotesParams? Result { get; private set; } public RelayCommand OkCommand { get; set; } public RelayCommand CancelCommand { get; set; } - public IReadOnlyCollection Tags + public IReadOnlyCollection? Tags { get => _tags; set diff --git a/JWLMerge/ViewModel/MainViewModel.cs b/JWLMerge/ViewModel/MainViewModel.cs index 0dc311f..d99c880 100644 --- a/JWLMerge/ViewModel/MainViewModel.cs +++ b/JWLMerge/ViewModel/MainViewModel.cs @@ -9,14 +9,14 @@ namespace JWLMerge.ViewModel using System.Linq; using System.Threading.Tasks; using System.Windows; - using JWLMerge.BackupFileServices; + using BackupFileServices; using JWLMerge.BackupFileServices.Helpers; using JWLMerge.BackupFileServices.Models.DatabaseModels; - using JWLMerge.ExcelServices; - using JWLMerge.Helpers; - using JWLMerge.Messages; - using JWLMerge.Models; - using JWLMerge.Services; + using ExcelServices; + using Helpers; + using Messages; + using Models; + using Services; using MaterialDesignThemes.Wpf; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; @@ -24,7 +24,7 @@ namespace JWLMerge.ViewModel using Serilog; // ReSharper disable once ClassNeverInstantiated.Global - internal class MainViewModel : ObservableObject + internal sealed class MainViewModel : ObservableObject { private readonly string _latestReleaseUrl = Properties.Resources.LATEST_RELEASE_URL; private readonly IDragDropService _dragDropService; @@ -69,9 +69,9 @@ namespace JWLMerge.ViewModel GetVersionData(); } - public ObservableCollection Files { get; } = new ObservableCollection(); + public ObservableCollection Files { get; } = new(); - public string Title { get; set; } + public string Title { get; private set; } = null!; public bool FileListEmpty => Files.Count == 0; @@ -86,8 +86,8 @@ namespace JWLMerge.ViewModel OnPropertyChanged(); OnPropertyChanged(nameof(IsNotBusy)); - MergeCommand?.NotifyCanExecuteChanged(); - CloseCardCommand?.NotifyCanExecuteChanged(); + MergeCommand.NotifyCanExecuteChanged(); + CloseCardCommand.NotifyCanExecuteChanged(); } } } @@ -113,31 +113,31 @@ namespace JWLMerge.ViewModel public ISnackbarMessageQueue TheSnackbarMessageQueue => _snackbarService.TheSnackbarMessageQueue; // commands... - public RelayCommand CloseCardCommand { get; set; } + public RelayCommand CloseCardCommand { get; private set; } = null!; - public RelayCommand ShowDetailsCommand { get; set; } + public RelayCommand ShowDetailsCommand { get; private set; } = null!; - public RelayCommand MergeCommand { get; set; } + public RelayCommand MergeCommand { get; private set; } = null!; - public RelayCommand HomepageCommand { get; set; } + public RelayCommand HomepageCommand { get; private set; } = null!; - public RelayCommand UpdateCommand { get; set; } + public RelayCommand UpdateCommand { get; private set; } = null!; - public RelayCommand RemoveFavouritesCommand { get; set; } + public RelayCommand RemoveFavouritesCommand { get; private set; } = null!; - public RelayCommand RedactNotesCommand { get; set; } + public RelayCommand RedactNotesCommand { get; private set; } = null!; - public RelayCommand ImportBibleNotesCommand { get; set; } + public RelayCommand ImportBibleNotesCommand { get; private set; } = null!; - public RelayCommand ExportBibleNotesCommand { get; set; } + public RelayCommand ExportBibleNotesCommand { get; private set; } = null!; - public RelayCommand RemoveNotesByTagCommand { get; set; } + public RelayCommand RemoveNotesByTagCommand { get; private set; } = null!; - public RelayCommand RemoveUnderliningByColourCommand { get; set; } + public RelayCommand RemoveUnderliningByColourCommand { get; private set; } = null!; - public RelayCommand RemoveUnderliningByPubAndColourCommand { get; set; } + public RelayCommand RemoveUnderliningByPubAndColourCommand { get; private set; } = null!; - private JwLibraryFile GetFile(string filePath) + private JwLibraryFile? GetFile(string? filePath) { var file = Files.SingleOrDefault(x => x.FilePath.Equals(filePath)); if (file == null) @@ -158,34 +158,38 @@ namespace JWLMerge.ViewModel } private void FilesCollectionChanged( - object sender, + object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(FileListEmpty)); OnPropertyChanged(nameof(MergeCommandCaption)); - MergeCommand?.NotifyCanExecuteChanged(); + MergeCommand.NotifyCanExecuteChanged(); } private void InitCommands() { - CloseCardCommand = new RelayCommand(RemoveCard, filePath => !IsBusy && !_dialogService.IsDialogVisible()); - ShowDetailsCommand = new RelayCommand(ShowDetails, filePath => !IsBusy); + CloseCardCommand = new RelayCommand(RemoveCard, _ => !IsBusy && !_dialogService.IsDialogVisible()); + ShowDetailsCommand = new RelayCommand(ShowDetails, _ => !IsBusy); MergeCommand = new RelayCommand(MergeFiles, () => GetMergeableFileCount() > 0 && !IsBusy && !_dialogService.IsDialogVisible()); HomepageCommand = new RelayCommand(LaunchHomepage); UpdateCommand = new RelayCommand(LaunchLatestReleasePage); - RemoveFavouritesCommand = new RelayCommand(async (filePath) => await RemoveFavouritesAsync(filePath), filePath => !IsBusy); - RedactNotesCommand = new RelayCommand(async (filePath) => await RedactNotesAsync(filePath), filePath => !IsBusy); - ImportBibleNotesCommand = new RelayCommand(async (filePath) => await ImportBibleNotesAsync(filePath), filePath => !IsBusy); - ExportBibleNotesCommand = new RelayCommand(async (filePath) => await ExportBibleNotesAsync(filePath), filePath => !IsBusy); - RemoveNotesByTagCommand = new RelayCommand(async (filePath) => await RemoveNotesByTagAsync(filePath), filePath => !IsBusy); - RemoveUnderliningByColourCommand = new RelayCommand(async (filePath) => await RemoveUnderliningByColourAsync(filePath), filePath => !IsBusy); - RemoveUnderliningByPubAndColourCommand = new RelayCommand(async (filePath) => await RemoveUnderliningByPubAndColourAsync(filePath), filePath => !IsBusy); + RemoveFavouritesCommand = new RelayCommand(async filePath => await RemoveFavouritesAsync(filePath), _ => !IsBusy); + RedactNotesCommand = new RelayCommand(async filePath => await RedactNotesAsync(filePath), _ => !IsBusy); + ImportBibleNotesCommand = new RelayCommand(async filePath => await ImportBibleNotesAsync(filePath), _ => !IsBusy); + ExportBibleNotesCommand = new RelayCommand(async filePath => await ExportBibleNotesAsync(filePath), _ => !IsBusy); + RemoveNotesByTagCommand = new RelayCommand(async filePath => await RemoveNotesByTagAsync(filePath), _ => !IsBusy); + RemoveUnderliningByColourCommand = new RelayCommand(async filePath => await RemoveUnderliningByColourAsync(filePath), _ => !IsBusy); + RemoveUnderliningByPubAndColourCommand = new RelayCommand(async filePath => await RemoveUnderliningByPubAndColourAsync(filePath), _ => !IsBusy); } - private async Task ExportBibleNotesAsync(string filePath) + private async Task ExportBibleNotesAsync(string? filePath) { var file = GetFile(filePath); + if (file == null) + { + return; + } var bibleNotesExportFilePath = _fileOpenSaveService.GetBibleNotesExportFilePath("Bible Notes File"); if (string.IsNullOrWhiteSpace(bibleNotesExportFilePath)) @@ -217,9 +221,13 @@ namespace JWLMerge.ViewModel } } - private async Task ImportBibleNotesAsync(string filePath) + private async Task ImportBibleNotesAsync(string? filePath) { var file = GetFile(filePath); + if (file == null) + { + return; + } var bibleNotesImportFilePath = _fileOpenSaveService.GetBibleNotesImportFilePath("Bible Notes File"); if (string.IsNullOrWhiteSpace(bibleNotesImportFilePath)) @@ -227,7 +235,7 @@ namespace JWLMerge.ViewModel return; } - var userDefinedTags = file.BackupFile.Database.Tags.Where(x => x.Type != 0) + var userDefinedTags = file!.BackupFile.Database.Tags.Where(x => x.Type != 0) .OrderBy(x => x.Name) .ToList(); @@ -256,7 +264,7 @@ namespace JWLMerge.ViewModel _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, file.FilePath, file.FilePath); }); - _windowService.Close(filePath); + _windowService.Close(filePath!); _snackbarService.Enqueue("Bible notes imported successfully"); file.RefreshTooltipSummary(); @@ -272,9 +280,13 @@ namespace JWLMerge.ViewModel } } - private async Task RemoveUnderliningByPubAndColourAsync(string filePath) + private async Task RemoveUnderliningByPubAndColourAsync(string? filePath) { var file = GetFile(filePath); + if (file == null) + { + return; + } var colors = ColourHelper.GetHighlighterColoursInUse(file.BackupFile.Database.UserMarks, true); var pubs = PublicationHelper.GetPublications(file.BackupFile.Database.Locations, file.BackupFile.Database.UserMarks, true); @@ -296,11 +308,11 @@ namespace JWLMerge.ViewModel if (underliningRemoved > 0) { - _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, filePath, filePath); + _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, filePath!, filePath!); } }); - _windowService.Close(filePath); + _windowService.Close(filePath!); _snackbarService.Enqueue(underliningRemoved == 0 ? "There was no underlining to remove!" @@ -319,9 +331,13 @@ namespace JWLMerge.ViewModel } } - private async Task RemoveUnderliningByColourAsync(string filePath) + private async Task RemoveUnderliningByColourAsync(string? filePath) { var file = GetFile(filePath); + if (file == null) + { + return; + } if (!file.BackupFile.Database.UserMarks.Any()) { @@ -349,11 +365,11 @@ namespace JWLMerge.ViewModel if (underliningRemoved > 0) { - _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, filePath, filePath); + _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, filePath!, filePath!); } }); - _windowService.Close(filePath); + _windowService.Close(filePath!); _snackbarService.Enqueue(underliningRemoved == 0 ? "There was no underlining to remove!" @@ -372,9 +388,13 @@ namespace JWLMerge.ViewModel } } - private async Task RemoveNotesByTagAsync(string filePath) + private async Task RemoveNotesByTagAsync(string? filePath) { var file = GetFile(filePath); + if (file == null) + { + return; + } var tags = TagHelper.GetTagsInUseByNotes(file.BackupFile.Database); @@ -403,11 +423,11 @@ namespace JWLMerge.ViewModel if (notesRemovedCount > 0) { - _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, filePath, filePath); + _backupFileService.WriteNewDatabaseWithClean(file.BackupFile, filePath!, filePath!); } }); - _windowService.Close(filePath); + _windowService.Close(filePath!); _snackbarService.Enqueue(notesRemovedCount == 0 ? "There were no notes to remove!" @@ -426,18 +446,18 @@ namespace JWLMerge.ViewModel } } - private async Task RedactNotesAsync(string filePath) + private async Task RedactNotesAsync(string? filePath) { var file = GetFile(filePath); - var notes = file.BackupFile?.Database.Notes; + var notes = file?.BackupFile.Database.Notes; if (notes == null || !notes.Any()) { _snackbarService.Enqueue("No notes found"); return; } - if (file.NotesRedacted) + if (file!.NotesRedacted) { _snackbarService.Enqueue("Notes already obfuscated"); return; @@ -452,10 +472,10 @@ namespace JWLMerge.ViewModel await Task.Run(() => { count = _backupFileService.RedactNotes(file.BackupFile); - _backupFileService.WriteNewDatabase(file.BackupFile, filePath, filePath); + _backupFileService.WriteNewDatabase(file.BackupFile, filePath!, filePath!); }); - _windowService.Close(filePath); + _windowService.Close(filePath!); file.NotesRedacted = true; _snackbarService.Enqueue($"{count} Notes obfuscated successfully"); } @@ -471,11 +491,11 @@ namespace JWLMerge.ViewModel } } - private async Task RemoveFavouritesAsync(string filePath) + private async Task RemoveFavouritesAsync(string? filePath) { var file = GetFile(filePath); - var favourites = file.BackupFile?.Database.TagMaps.Where(x => x.TagId == 1); + var favourites = file?.BackupFile.Database.TagMaps.Where(x => x.TagId == 1); if (favourites == null || !favourites.Any()) { _snackbarService.Enqueue("No favourites found"); @@ -489,14 +509,14 @@ namespace JWLMerge.ViewModel { await Task.Run(() => { - _backupFileService.RemoveFavourites(file.BackupFile); - _backupFileService.WriteNewDatabase(file.BackupFile, filePath, filePath); + _backupFileService.RemoveFavourites(file!.BackupFile); + _backupFileService.WriteNewDatabase(file.BackupFile, filePath!, filePath!); }); _snackbarService.Enqueue("Favourites removed successfully"); - _windowService.Close(filePath); + _windowService.Close(filePath!); - file.RefreshTooltipSummary(); + file!.RefreshTooltipSummary(); } catch (Exception ex) { @@ -568,13 +588,7 @@ namespace JWLMerge.ViewModel // applying any merge parameters. ReloadFiles(); } - }).ContinueWith(previousTask => - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - IsBusy = false; - })); - }); + }).ContinueWith(_ => Application.Current.Dispatcher.BeginInvoke(new Action(() => IsBusy = false))); } private void ApplyMergeParameters() @@ -606,7 +620,7 @@ namespace JWLMerge.ViewModel }); } - private string GetSuitableFilePathForSchema() + private string? GetSuitableFilePathForSchema() { foreach (var file in Files) { @@ -619,8 +633,13 @@ namespace JWLMerge.ViewModel return null; } - private void RemoveCard(string filePath) + private void RemoveCard(string? filePath) { + if (string.IsNullOrEmpty(filePath)) + { + return; + } + foreach (var file in Files) { if (IsSameFile(file.FilePath, filePath)) @@ -632,8 +651,13 @@ namespace JWLMerge.ViewModel } } - private void ShowDetails(string filePath) + private void ShowDetails(string? filePath) { + if (string.IsNullOrEmpty(filePath)) + { + return; + } + var file = GetFile(filePath); if (file != null) { @@ -704,11 +728,7 @@ namespace JWLMerge.ViewModel { var backupFile = _backupFileService.Load(file); - tmpFilesCollection.Add(new JwLibraryFile - { - BackupFile = backupFile, - FilePath = file, - }); + tmpFilesCollection.Add(new JwLibraryFile(file, backupFile)); }); foreach (var file in tmpFilesCollection) @@ -721,14 +741,14 @@ namespace JWLMerge.ViewModel } } - private void FilePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void FilePropertyChanged(object? sender, PropertyChangedEventArgs e) { // when the merge params are modified it can leave the number of mergeable items at less than 2. - MergeCommand?.NotifyCanExecuteChanged(); + MergeCommand.NotifyCanExecuteChanged(); OnPropertyChanged(nameof(MergeCommandCaption)); } - private bool IsSameFile(string path1, string path2) + private static bool IsSameFile(string path1, string path2) { return Path.GetFullPath(path1).Equals(Path.GetFullPath(path2), StringComparison.OrdinalIgnoreCase); } @@ -767,7 +787,7 @@ namespace JWLMerge.ViewModel } } - private bool IsInDesignMode() + private static bool IsInDesignMode() { #if DEBUG DependencyObject dep = new(); diff --git a/JWLMerge/ViewModel/RedactNotesPromptViewModel.cs b/JWLMerge/ViewModel/RedactNotesPromptViewModel.cs index 8d397b0..32a2136 100644 --- a/JWLMerge/ViewModel/RedactNotesPromptViewModel.cs +++ b/JWLMerge/ViewModel/RedactNotesPromptViewModel.cs @@ -5,7 +5,7 @@ using Microsoft.Toolkit.Mvvm.Input; // ReSharper disable once ClassNeverInstantiated.Global - internal class RedactNotesPromptViewModel : ObservableObject + internal sealed class RedactNotesPromptViewModel : ObservableObject { public RedactNotesPromptViewModel() { @@ -13,9 +13,9 @@ NoCommand = new RelayCommand(No); } - public RelayCommand YesCommand { get; set; } + public RelayCommand YesCommand { get; } - public RelayCommand NoCommand { get; set; } + public RelayCommand NoCommand { get; } public bool Result { get; private set; } diff --git a/JWLMerge/ViewModel/RemoveFavouritesPromptViewModel.cs b/JWLMerge/ViewModel/RemoveFavouritesPromptViewModel.cs index 6f90bf5..4ea8bc9 100644 --- a/JWLMerge/ViewModel/RemoveFavouritesPromptViewModel.cs +++ b/JWLMerge/ViewModel/RemoveFavouritesPromptViewModel.cs @@ -5,7 +5,7 @@ using Microsoft.Toolkit.Mvvm.Input; // ReSharper disable once ClassNeverInstantiated.Global - internal class RemoveFavouritesPromptViewModel : ObservableObject + internal sealed class RemoveFavouritesPromptViewModel : ObservableObject { public RemoveFavouritesPromptViewModel() { @@ -13,9 +13,9 @@ NoCommand = new RelayCommand(No); } - public RelayCommand YesCommand { get; set; } + public RelayCommand YesCommand { get; } - public RelayCommand NoCommand { get; set; } + public RelayCommand NoCommand { get; } public bool Result { get; private set; } diff --git a/JWLMerge/ViewModel/RemoveNotesByTagViewModel.cs b/JWLMerge/ViewModel/RemoveNotesByTagViewModel.cs index cdde1ba..7a09d8a 100644 --- a/JWLMerge/ViewModel/RemoveNotesByTagViewModel.cs +++ b/JWLMerge/ViewModel/RemoveNotesByTagViewModel.cs @@ -3,14 +3,14 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; - using JWLMerge.Models; - using JWLMerge.Services; + using Models; + using Services; using MaterialDesignThemes.Wpf; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; // ReSharper disable once ClassNeverInstantiated.Global - internal class RemoveNotesByTagViewModel : ObservableObject + internal sealed class RemoveNotesByTagViewModel : ObservableObject { private bool _removeAssociatedUnderlining; private bool _removeAssociatedTags; @@ -23,13 +23,13 @@ TagItems.CollectionChanged += TagItemsCollectionChanged; } - public RelayCommand OkCommand { get; set; } + public RelayCommand OkCommand { get; } - public RelayCommand CancelCommand { get; set; } + public RelayCommand CancelCommand { get; } - public int[] Result { get; private set; } + public int[]? Result { get; private set; } - public ObservableCollection TagItems { get; } = new ObservableCollection(); + public ObservableCollection TagItems { get; } = new(); public bool SelectionMade => TagItems.Any(x => x.IsChecked); @@ -49,9 +49,9 @@ ? "Remove associated Tags" : "Remove associated Tag"; - private void TagItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + private void TagItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (e.Action == NotifyCollectionChangedAction.Add) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { foreach (TagListItem item in e.NewItems) { @@ -65,7 +65,7 @@ return TagItems.Count(x => x.IsChecked && x.Id != DialogService.UntaggedItemId); } - private void ItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void ItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { OnPropertyChanged(nameof(SelectionMade)); OnPropertyChanged(nameof(RemoveTagsCaption)); diff --git a/JWLMerge/ViewModel/RemoveUnderliningByColourViewModel.cs b/JWLMerge/ViewModel/RemoveUnderliningByColourViewModel.cs index 4a7aec5..1c84996 100644 --- a/JWLMerge/ViewModel/RemoveUnderliningByColourViewModel.cs +++ b/JWLMerge/ViewModel/RemoveUnderliningByColourViewModel.cs @@ -3,13 +3,13 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; - using JWLMerge.Models; + using Models; using MaterialDesignThemes.Wpf; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; // ReSharper disable once ClassNeverInstantiated.Global - internal class RemoveUnderliningByColourViewModel : ObservableObject + internal sealed class RemoveUnderliningByColourViewModel : ObservableObject { private bool _removeAssociatedNotes; @@ -21,15 +21,15 @@ ColourItems.CollectionChanged += ItemsCollectionChanged; } - public RelayCommand OkCommand { get; set; } + public RelayCommand OkCommand { get; } - public RelayCommand CancelCommand { get; set; } + public RelayCommand CancelCommand { get; } - public ObservableCollection ColourItems { get; } = new ObservableCollection(); + public ObservableCollection ColourItems { get; } = new(); public bool SelectionMade => ColourItems.Any(x => x.IsChecked); - public int[] Result { get; private set; } + public int[]? Result { get; private set; } public bool RemoveAssociatedNotes { @@ -37,9 +37,9 @@ set => SetProperty(ref _removeAssociatedNotes, value); } - private void ItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + private void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - if (e.Action == NotifyCollectionChangedAction.Add) + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { foreach (ColourListItem item in e.NewItems) { @@ -48,7 +48,7 @@ } } - private void ItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void ItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { OnPropertyChanged(nameof(SelectionMade)); } diff --git a/JWLMerge/ViewModel/RemoveUnderliningByPubAndColourViewModel.cs b/JWLMerge/ViewModel/RemoveUnderliningByPubAndColourViewModel.cs index 54f3276..09f32a0 100644 --- a/JWLMerge/ViewModel/RemoveUnderliningByPubAndColourViewModel.cs +++ b/JWLMerge/ViewModel/RemoveUnderliningByPubAndColourViewModel.cs @@ -1,16 +1,16 @@ namespace JWLMerge.ViewModel { using System.Collections.ObjectModel; - using JWLMerge.Models; + using Models; using MaterialDesignThemes.Wpf; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; - internal class RemoveUnderliningByPubAndColourViewModel : ObservableObject + internal sealed class RemoveUnderliningByPubAndColourViewModel : ObservableObject { private bool _removeAssociatedNotes; - private PublicationDef _selectedPublication; - private ColourListItem _selectedColour; + private PublicationDef? _selectedPublication; + private ColourListItem? _selectedColour; public RemoveUnderliningByPubAndColourViewModel() { @@ -18,15 +18,15 @@ CancelCommand = new RelayCommand(Cancel); } - public RelayCommand OkCommand { get; set; } + public RelayCommand OkCommand { get; } - public RelayCommand CancelCommand { get; set; } + public RelayCommand CancelCommand { get; } - public ObservableCollection PublicationList { get; } = new ObservableCollection(); + public ObservableCollection PublicationList { get; } = new(); - public ObservableCollection ColourItems { get; } = new ObservableCollection(); + public ObservableCollection ColourItems { get; } = new(); - public PublicationDef SelectedPublication + public PublicationDef? SelectedPublication { get => _selectedPublication; set @@ -40,7 +40,7 @@ } } - public ColourListItem SelectedColour + public ColourListItem? SelectedColour { get => _selectedColour; set @@ -56,7 +56,7 @@ public bool SelectionMade => SelectedPublication != null && SelectedColour != null; - public PubColourResult Result { get; private set; } + public PubColourResult? Result { get; private set; } public bool RemoveAssociatedNotes { diff --git a/JWLMerge/ViewModel/ViewModelLocator.cs b/JWLMerge/ViewModel/ViewModelLocator.cs index 40cbd98..cd540ca 100644 --- a/JWLMerge/ViewModel/ViewModelLocator.cs +++ b/JWLMerge/ViewModel/ViewModelLocator.cs @@ -6,7 +6,7 @@ namespace JWLMerge.ViewModel /// This class contains static references to all the view models in the /// application and provides an entry point for the bindings. /// - internal class ViewModelLocator + internal sealed class ViewModelLocator { public static MainViewModel Main => Ioc.Default.GetService()!; diff --git a/JWLMergeCLI/.editorconfig b/JWLMergeCLI/.editorconfig new file mode 100644 index 0000000..c4fd20d --- /dev/null +++ b/JWLMergeCLI/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] + +# S112: General exceptions should never be thrown +dotnet_diagnostic.S112.severity = silent + +# U2U1202: Use LINQ Count methods efficiently +dotnet_diagnostic.U2U1202.severity = silent diff --git a/JWLMergeCLI/Args/ArgsHelper.cs b/JWLMergeCLI/Args/ArgsHelper.cs index c765327..5bfe71e 100644 --- a/JWLMergeCLI/Args/ArgsHelper.cs +++ b/JWLMergeCLI/Args/ArgsHelper.cs @@ -12,7 +12,7 @@ AwaitingOutput, } - public static CommandLineArgs Parse(string[] args) + public static CommandLineArgs? Parse(string[]? args) { if (args == null || args.Length < 2) { diff --git a/JWLMergeCLI/Args/CommandLineArgs.cs b/JWLMergeCLI/Args/CommandLineArgs.cs index f632d48..0895a52 100644 --- a/JWLMergeCLI/Args/CommandLineArgs.cs +++ b/JWLMergeCLI/Args/CommandLineArgs.cs @@ -1,9 +1,9 @@ namespace JWLMergeCLI.Args { - internal class CommandLineArgs + internal sealed class CommandLineArgs { - public string[] BackupFiles { get; set; } + public string[] BackupFiles { get; set; } = null!; - public string OutputFilePath { get; set; } + public string? OutputFilePath { get; set; } } } diff --git a/JWLMergeCLI/JWLMergeCLI.csproj b/JWLMergeCLI/JWLMergeCLI.csproj index 16399fa..755099e 100644 --- a/JWLMergeCLI/JWLMergeCLI.csproj +++ b/JWLMergeCLI/JWLMergeCLI.csproj @@ -8,6 +8,10 @@ JWLMerge.ico + + + + diff --git a/JWLMergeCLI/MainApp.cs b/JWLMergeCLI/MainApp.cs index dc27429..eb5723e 100644 --- a/JWLMergeCLI/MainApp.cs +++ b/JWLMergeCLI/MainApp.cs @@ -4,7 +4,7 @@ using System.Linq; using JWLMerge.BackupFileServices; using JWLMerge.BackupFileServices.Events; - using JWLMergeCLI.Args; + using Args; using Serilog; /// @@ -12,7 +12,7 @@ /// internal sealed class MainApp { - public event EventHandler ProgressEvent; + public event EventHandler? ProgressEvent; /// /// Runs the app. @@ -20,7 +20,7 @@ /// Program arguments public void Run(CommandLineArgs args) { - IBackupFileService backupFileService = new BackupFileService(); + var backupFileService = new BackupFileService(); backupFileService.ProgressEvent += BackupFileServiceProgress; var backup = backupFileService.Merge(args.BackupFiles); @@ -32,7 +32,7 @@ OnProgressEvent(logMessage); } - private void BackupFileServiceProgress(object sender, ProgressEventArgs e) + private void BackupFileServiceProgress(object? sender, ProgressEventArgs e) { OnProgressEvent(e); } @@ -44,7 +44,7 @@ private void OnProgressEvent(string message) { - OnProgressEvent(new ProgressEventArgs { Message = message }); + OnProgressEvent(new ProgressEventArgs(message)); } } } diff --git a/JWLMergeCLI/Program.cs b/JWLMergeCLI/Program.cs index dae8583..beb49b3 100644 --- a/JWLMergeCLI/Program.cs +++ b/JWLMergeCLI/Program.cs @@ -3,7 +3,7 @@ using System; using System.Diagnostics; using System.IO; - using JWLMergeCLI.Args; + using Args; using Serilog; public static class Program @@ -71,7 +71,7 @@ { System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); - return fvi.FileVersion; + return fvi.FileVersion ?? "Unknown"; } private static void ShowUsage() @@ -79,36 +79,36 @@ Console.ForegroundColor = ConsoleColor.DarkBlue; Console.BackgroundColor = ConsoleColor.White; Console.WriteLine(); - Console.WriteLine($" JWLMergeCLI version {GetVersion()} "); + Console.WriteLine($@" JWLMergeCLI version {GetVersion()} "); Console.WriteLine(); Console.ResetColor(); Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine(" Description:"); + Console.WriteLine(@" Description:"); Console.ResetColor(); - Console.WriteLine(" JWLMergeCLI is used to merge the contents of 2 or more jwlibrary backup"); - Console.WriteLine(" files. These files are produced by the JW Library backup command and"); - Console.WriteLine(" contain your personal study notes and highlighting."); + Console.WriteLine(@" JWLMergeCLI is used to merge the contents of 2 or more jwlibrary backup"); + Console.WriteLine(@" files. These files are produced by the JW Library backup command and"); + Console.WriteLine(@" contain your personal study notes and highlighting."); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine(" Usage:"); + Console.WriteLine(@" Usage:"); Console.ResetColor(); - Console.WriteLine(" JWLMergeCLI ... [-o output file]"); + Console.WriteLine(@" JWLMergeCLI ... [-o output file]"); Console.WriteLine(); - Console.WriteLine(" Note that you can optionally specify the full path and name of the merged"); - Console.WriteLine(" file using the -o (or --output directive). If you omit it, the merged"); - Console.WriteLine(" file is stored in the current folder."); + Console.WriteLine(@" Note that you can optionally specify the full path and name of the merged"); + Console.WriteLine(@" file using the -o (or --output directive). If you omit it, the merged"); + Console.WriteLine(@" file is stored in the current folder."); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine(" An example:"); + Console.WriteLine(@" An example:"); Console.ResetColor(); - Console.WriteLine(" JWLMergeCLI \"C:\\Backup_PC16.jwlibrary\" \"C:\\Backup_iPad.jwlibrary\""); + Console.WriteLine(@" JWLMergeCLI ""C:\Backup_PC16.jwlibrary"" ""C:\Backup_iPad.jwlibrary"""); Console.WriteLine(); } - private static void AppProgress(object sender, JWLMerge.BackupFileServices.Events.ProgressEventArgs e) + private static void AppProgress(object? sender, JWLMerge.BackupFileServices.Events.ProgressEventArgs e) { Console.WriteLine(e.Message); }