WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit d2d17ec

Browse files
committed
Enhance KeeShare synchronization logic by implementing non-destructive merge for "Synchronize" mode and clarifying behavior for "Import" mode.
1 parent 58e4b42 commit d2d17ec

File tree

1 file changed

+65
-15
lines changed

1 file changed

+65
-15
lines changed

src/keepass2android-app/KeeShare.cs

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,14 @@ private void Import(PwGroup targetGroup, string path, string password, string ty
289289
/// <summary>
290290
/// Synchronizes the target group with the source group from the shared database.
291291
///
292-
/// WARNING: This is a destructive operation that will overwrite all entries and subgroups
293-
/// in the target group with content from the source. Any local modifications to entries
294-
/// within a KeeShare group will be lost on each sync.
292+
/// For "Import" mode: Performs a destructive replace - clears target and copies all
293+
/// content from source. Any local modifications will be lost.
295294
///
296-
/// This behavior is intentional for "Import" mode (one-time import), but for "Synchronize"
297-
/// mode, a proper merge implementation would be preferable to preserve local modifications
298-
/// that haven't been synced back to the shared database.
295+
/// For "Synchronize" mode: Performs a non-destructive merge - adds new entries/groups
296+
/// from source, and updates existing entries/groups if the source version is newer
297+
/// (based on LastModificationTime). Local entries not in source are preserved.
299298
///
300-
/// The following properties of the target group are preserved:
299+
/// The following properties of the target group are always preserved:
301300
/// - UUID (group identity)
302301
/// - Parent (group hierarchy)
303302
/// - Name (local group name)
@@ -309,11 +308,26 @@ private void Import(PwGroup targetGroup, string path, string password, string ty
309308
/// <param name="type">KeeShare type: "Import" or "Synchronize"</param>
310309
private void SyncGroups(PwGroup source, PwGroup target, string type)
311310
{
312-
// TODO: For "Synchronize" mode, consider implementing proper merge logic using
313-
// KeePassLib's merge functionality (e.g., PwDatabase.MergeIn) to preserve local
314-
// modifications and handle conflicts appropriately.
311+
if (type == "Synchronize")
312+
{
313+
// Non-destructive merge: add new items, update existing if newer
314+
MergeGroupContents(source, target);
315+
}
316+
else
317+
{
318+
// Import mode: destructive replace
319+
ImportGroupContents(source, target);
320+
}
315321

316-
// Clear entries and subgroups (destructive operation)
322+
target.Touch(true, false);
323+
}
324+
325+
/// <summary>
326+
/// Performs a destructive import: clears target and copies all content from source.
327+
/// </summary>
328+
private void ImportGroupContents(PwGroup source, PwGroup target)
329+
{
330+
// Clear entries and subgroups
317331
target.Entries.Clear();
318332
target.Groups.Clear();
319333

@@ -328,11 +342,47 @@ private void SyncGroups(PwGroup source, PwGroup target, string type)
328342
{
329343
target.AddGroup(group.CloneDeep(), true);
330344
}
345+
}
346+
347+
/// <summary>
348+
/// Performs a non-destructive merge: adds new entries/groups from source,
349+
/// updates existing if source is newer. Local items not in source are preserved.
350+
/// </summary>
351+
private void MergeGroupContents(PwGroup source, PwGroup target)
352+
{
353+
// Merge entries
354+
foreach (var sourceEntry in source.Entries)
355+
{
356+
var targetEntry = target.FindEntry(sourceEntry.Uuid, false);
357+
if (targetEntry == null)
358+
{
359+
// Entry doesn't exist in target - add it
360+
target.AddEntry(sourceEntry.CloneDeep(), true);
361+
}
362+
else
363+
{
364+
// Entry exists - update if source is newer
365+
// AssignProperties with bOnlyIfNewer=true will only update if source.LastMod > target.LastMod
366+
targetEntry.AssignProperties(sourceEntry, true, false, false);
367+
}
368+
}
331369

332-
// Note: We preserve Name/Icon/Notes of the target group to maintain local identity
333-
// Only the content (entries and subgroups) is synchronized from the shared database.
334-
335-
target.Touch(true, false);
370+
// Merge subgroups recursively
371+
foreach (var sourceGroup in source.Groups)
372+
{
373+
var targetGroup = target.FindGroup(sourceGroup.Uuid, false);
374+
if (targetGroup == null)
375+
{
376+
// Group doesn't exist in target - add it
377+
target.AddGroup(sourceGroup.CloneDeep(), true);
378+
}
379+
else
380+
{
381+
// Group exists - update properties if source is newer, then merge contents
382+
targetGroup.AssignProperties(sourceGroup, true, false);
383+
MergeGroupContents(sourceGroup, targetGroup);
384+
}
385+
}
336386
}
337387

338388
private IOConnectionInfo ResolvePath(string path)

0 commit comments

Comments
 (0)