mirror of
https://github.com/AntonyCorbett/JWLMerge
synced 2026-07-31 17:15:48 -04:00
379 lines
13 KiB
C#
379 lines
13 KiB
C#
namespace JWLMerge.BackupFileServices.Helpers
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Events;
|
|
using Models.Database;
|
|
using Serilog;
|
|
|
|
/// <summary>
|
|
/// Merges the SQLite databases.
|
|
/// </summary>
|
|
internal sealed class Merger
|
|
{
|
|
private readonly IdTranslator _translatedLocationIds = new IdTranslator();
|
|
private readonly IdTranslator _translatedTagIds = new IdTranslator();
|
|
private readonly IdTranslator _translatedUserMarkIds = new IdTranslator();
|
|
private readonly IdTranslator _translatedNoteIds = new IdTranslator();
|
|
|
|
private int _maxLocationId;
|
|
private int _maxUserMarkId;
|
|
private int _maxNoteId;
|
|
private int _maxTagId;
|
|
private int _maxTagMapId;
|
|
private int _maxBlockRangeId;
|
|
private int _maxBookmarkId;
|
|
|
|
public event EventHandler<ProgressEventArgs> ProgressEvent;
|
|
|
|
/// <summary>
|
|
/// Merges the specified databases.
|
|
/// </summary>
|
|
/// <param name="databasesToMerge">The databases to merge.</param>
|
|
/// <returns><see cref="Database"/></returns>
|
|
public Database Merge(IEnumerable<Database> databasesToMerge)
|
|
{
|
|
var result = new Database();
|
|
result.InitBlank();
|
|
|
|
ClearMaxIds();
|
|
|
|
var databaseIndex = 1;
|
|
foreach (var database in databasesToMerge)
|
|
{
|
|
ProgressMessage($"MERGING DATABASE {databaseIndex++}:");
|
|
Merge(database, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void ClearMaxIds()
|
|
{
|
|
_maxLocationId = 0;
|
|
_maxUserMarkId = 0;
|
|
_maxNoteId = 0;
|
|
_maxTagId = 0;
|
|
_maxTagMapId = 0;
|
|
_maxBlockRangeId = 0;
|
|
_maxBookmarkId = 0;
|
|
}
|
|
|
|
private void Merge(Database source, Database destination)
|
|
{
|
|
ClearTranslators();
|
|
|
|
MergeUserMarks(source, destination);
|
|
MergeNotes(source, destination);
|
|
MergeTags(source, destination);
|
|
MergeTagMap(source, destination);
|
|
MergeBlockRanges(source, destination);
|
|
MergeBookmarks(source, destination);
|
|
|
|
ProgressMessage(" Checking validity");
|
|
destination.CheckValidity();
|
|
}
|
|
|
|
private void ClearTranslators()
|
|
{
|
|
_translatedLocationIds.Clear();
|
|
_translatedTagIds.Clear();
|
|
_translatedUserMarkIds.Clear();
|
|
_translatedNoteIds.Clear();
|
|
}
|
|
|
|
private void MergeBookmarks(Database source, Database destination)
|
|
{
|
|
ProgressMessage(" Bookmarks");
|
|
|
|
foreach (var bookmark in source.Bookmarks)
|
|
{
|
|
var location1 = source.FindLocation(bookmark.LocationId);
|
|
var location2 = source.FindLocation(bookmark.PublicationLocationId);
|
|
|
|
if (location1 != null && location2 != null)
|
|
{
|
|
InsertLocation(location1, destination);
|
|
InsertLocation(location2, destination);
|
|
|
|
var locationId = _translatedLocationIds.GetTranslatedId(bookmark.LocationId);
|
|
var publicationLocationId = _translatedLocationIds.GetTranslatedId(bookmark.PublicationLocationId);
|
|
if (locationId > 0 && publicationLocationId > 0)
|
|
{
|
|
var existingBookmark = destination.FindBookmark(locationId, publicationLocationId);
|
|
if (existingBookmark == null)
|
|
{
|
|
InsertBookmark(bookmark, destination);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (location1 == null)
|
|
{
|
|
Log.Logger.Error($"Could not find location for bookmark {bookmark.BookmarkId}");
|
|
}
|
|
|
|
if (location2 == null)
|
|
{
|
|
Log.Logger.Error($"Could not find publication location for bookmark {bookmark.BookmarkId}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InsertBookmark(Bookmark bookmark, Database destination)
|
|
{
|
|
Bookmark newBookmark = bookmark.Clone();
|
|
newBookmark.BookmarkId = ++_maxBookmarkId;
|
|
|
|
newBookmark.LocationId = _translatedLocationIds.GetTranslatedId(bookmark.LocationId);
|
|
newBookmark.PublicationLocationId = _translatedLocationIds.GetTranslatedId(bookmark.PublicationLocationId);
|
|
newBookmark.Slot = destination.GetNextBookmarkSlot(newBookmark.PublicationLocationId);
|
|
|
|
destination.Bookmarks.Add(newBookmark);
|
|
}
|
|
|
|
private void MergeBlockRanges(Database source, Database destination)
|
|
{
|
|
ProgressMessage(" Block ranges");
|
|
|
|
foreach (var range in source.BlockRanges)
|
|
{
|
|
var userMarkId = _translatedUserMarkIds.GetTranslatedId(range.UserMarkId);
|
|
var existingRange = destination.FindBlockRange(userMarkId);
|
|
if (existingRange == null)
|
|
{
|
|
InsertBlockRange(range, destination);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MergeTagMap(Database source, Database destination)
|
|
{
|
|
ProgressMessage(" Tag maps");
|
|
|
|
foreach (var tagMap in source.TagMaps)
|
|
{
|
|
var tagId = _translatedTagIds.GetTranslatedId(tagMap.TagId);
|
|
var typeId = 0;
|
|
|
|
switch (tagMap.Type)
|
|
{
|
|
case 0:
|
|
// a tag on a location
|
|
typeId = _translatedLocationIds.GetTranslatedId(tagMap.TypeId);
|
|
if (typeId == 0)
|
|
{
|
|
// must add location...
|
|
var location = source.FindLocation(tagMap.TypeId);
|
|
InsertLocation(location, destination);
|
|
typeId = _translatedLocationIds.GetTranslatedId(tagMap.TypeId);
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
// a tag on a Note
|
|
typeId = _translatedNoteIds.GetTranslatedId(tagMap.TypeId);
|
|
break;
|
|
}
|
|
|
|
if (typeId != 0)
|
|
{
|
|
var existingTagMap = destination.FindTagMap(tagId, typeId);
|
|
if (existingTagMap == null)
|
|
{
|
|
InsertTagMap(tagMap, destination);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MergeTags(Database source, Database destination)
|
|
{
|
|
ProgressMessage(" Tags");
|
|
|
|
foreach (var tag in source.Tags)
|
|
{
|
|
var existingTag = destination.FindTag(tag.Name);
|
|
if (existingTag != null)
|
|
{
|
|
_translatedTagIds.Add(tag.TagId, existingTag.TagId);
|
|
}
|
|
else
|
|
{
|
|
InsertTag(tag, destination);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MergeUserMarks(Database source, Database destination)
|
|
{
|
|
ProgressMessage(" User marks");
|
|
|
|
foreach (var userMark in source.UserMarks)
|
|
{
|
|
var existingUserMark = destination.FindUserMark(userMark.UserMarkGuid);
|
|
if (existingUserMark != null)
|
|
{
|
|
// user mark already exists in destination...
|
|
_translatedUserMarkIds.Add(userMark.UserMarkId, existingUserMark.UserMarkId);
|
|
}
|
|
else
|
|
{
|
|
var referencedLocation = userMark.LocationId;
|
|
var location = source.FindLocation(referencedLocation);
|
|
|
|
InsertLocation(location, destination);
|
|
InsertUserMark(userMark, destination);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InsertLocation(Location location, Database destination)
|
|
{
|
|
if (_translatedLocationIds.GetTranslatedId(location.LocationId) == 0)
|
|
{
|
|
var found = destination.FindLocationByValues(location);
|
|
if (found != null)
|
|
{
|
|
_translatedLocationIds.Add(location.LocationId, found.LocationId);
|
|
}
|
|
else
|
|
{
|
|
Location newLocation = location.Clone();
|
|
newLocation.LocationId = ++_maxLocationId;
|
|
destination.Locations.Add(newLocation);
|
|
|
|
_translatedLocationIds.Add(location.LocationId, newLocation.LocationId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InsertUserMark(UserMark userMark, Database destination)
|
|
{
|
|
UserMark newUserMark = userMark.Clone();
|
|
newUserMark.UserMarkId = ++_maxUserMarkId;
|
|
newUserMark.LocationId = _translatedLocationIds.GetTranslatedId(userMark.LocationId);
|
|
destination.UserMarks.Add(newUserMark);
|
|
|
|
_translatedUserMarkIds.Add(userMark.UserMarkId, newUserMark.UserMarkId);
|
|
}
|
|
|
|
private void InsertTag(Tag tag, Database destination)
|
|
{
|
|
Tag newTag = tag.Clone();
|
|
newTag.TagId = ++_maxTagId;
|
|
destination.Tags.Add(newTag);
|
|
|
|
_translatedTagIds.Add(tag.TagId, newTag.TagId);
|
|
}
|
|
|
|
private void InsertTagMap(TagMap tagMap, Database destination)
|
|
{
|
|
TagMap newTagMap = tagMap.Clone();
|
|
newTagMap.TagMapId = ++_maxTagMapId;
|
|
newTagMap.TagId = _translatedTagIds.GetTranslatedId(tagMap.TagId);
|
|
|
|
newTagMap.TypeId = 0;
|
|
|
|
switch (newTagMap.Type)
|
|
{
|
|
case 0:
|
|
newTagMap.TypeId = _translatedLocationIds.GetTranslatedId(tagMap.TypeId);
|
|
break;
|
|
|
|
case 1:
|
|
newTagMap.TypeId = _translatedNoteIds.GetTranslatedId(tagMap.TypeId);
|
|
break;
|
|
}
|
|
|
|
if (newTagMap.TypeId != 0)
|
|
{
|
|
destination.TagMaps.Add(newTagMap);
|
|
}
|
|
}
|
|
|
|
private void InsertNote(Note note, Database destination)
|
|
{
|
|
Note newNote = note.Clone();
|
|
newNote.NoteId = ++_maxNoteId;
|
|
|
|
if (note.UserMarkId != null)
|
|
{
|
|
newNote.UserMarkId = _translatedUserMarkIds.GetTranslatedId(note.UserMarkId.Value);
|
|
}
|
|
|
|
if (note.LocationId != null)
|
|
{
|
|
newNote.LocationId = _translatedLocationIds.GetTranslatedId(note.LocationId.Value);
|
|
}
|
|
|
|
destination.Notes.Add(newNote);
|
|
_translatedNoteIds.Add(note.NoteId, newNote.NoteId);
|
|
}
|
|
|
|
private void InsertBlockRange(BlockRange range, Database destination)
|
|
{
|
|
BlockRange newRange = range.Clone();
|
|
newRange.BlockRangeId = ++_maxBlockRangeId;
|
|
|
|
newRange.UserMarkId = _translatedUserMarkIds.GetTranslatedId(range.UserMarkId);
|
|
destination.BlockRanges.Add(newRange);
|
|
}
|
|
|
|
private void MergeNotes(Database source, Database destination)
|
|
{
|
|
ProgressMessage(" Notes");
|
|
|
|
foreach (var note in source.Notes)
|
|
{
|
|
var existingNote = destination.FindNote(note.Guid);
|
|
if (existingNote != null)
|
|
{
|
|
// note already exists in destination...
|
|
if (existingNote.GetLastModifiedDateTime() < note.GetLastModifiedDateTime())
|
|
{
|
|
// ...but it's older
|
|
UpdateNote(note, existingNote);
|
|
}
|
|
|
|
_translatedNoteIds.Add(note.NoteId, existingNote.NoteId);
|
|
}
|
|
else
|
|
{
|
|
// a new note...
|
|
if (note.LocationId != null && _translatedLocationIds.GetTranslatedId(note.LocationId.Value) == 0)
|
|
{
|
|
var referencedLocation = note.LocationId.Value;
|
|
var location = source.FindLocation(referencedLocation);
|
|
|
|
InsertLocation(location, destination);
|
|
}
|
|
|
|
InsertNote(note, destination);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateNote(Note source, Note destination)
|
|
{
|
|
destination.Title = source.Title;
|
|
destination.Content = source.Content;
|
|
destination.LastModified = source.LastModified;
|
|
}
|
|
|
|
private void OnProgressEvent(string message)
|
|
{
|
|
ProgressEvent?.Invoke(this, new ProgressEventArgs { Message = message });
|
|
}
|
|
|
|
private void ProgressMessage(string logMessage)
|
|
{
|
|
Log.Logger.Information(logMessage);
|
|
OnProgressEvent(logMessage);
|
|
}
|
|
}
|
|
}
|