@@ -54,7 +54,9 @@ import io.github.sds100.keymapper.util.Error
5454import io.github.sds100.keymapper.util.Result
5555import io.github.sds100.keymapper.util.State
5656import io.github.sds100.keymapper.util.Success
57+ import io.github.sds100.keymapper.util.TreeNode
5758import io.github.sds100.keymapper.util.UuidGenerator
59+ import io.github.sds100.keymapper.util.breadFirstTraversal
5860import io.github.sds100.keymapper.util.onFailure
5961import io.github.sds100.keymapper.util.then
6062import kotlinx.coroutines.CoroutineScope
@@ -473,55 +475,11 @@ class BackupManagerImpl(
473475
474476 // Group parents must be restored first so an SqliteConstraintException
475477 // is not thrown when restoring a child group.
476- val groupsToRestoreMap = backupContent.groups.associateBy { it.uid }.toMutableMap()
477- val groupRestoreQueue = LinkedList <GroupEntity >()
478+ val groupRestoreTrees = buildGroupTrees(backupContent.groups)
478479
479- // Order the groups into a queue such that a parent is always before a child.
480- for (group in backupContent.groups) {
481- if (groupsToRestoreMap.containsKey(group.uid)) {
482- groupRestoreQueue.addFirst(group)
483- }
484-
485- var parent = groupsToRestoreMap[group.parentUid]
486-
487- while (parent != null ) {
488- groupRestoreQueue.addFirst(parent)
489- groupsToRestoreMap.remove(parent.uid)
490- parent = groupsToRestoreMap[parent.parentUid]
491- }
492- }
493-
494- for (group in groupRestoreQueue) {
495- // Set the last opened date to now so that the imported group
496- // shows as the most recent.
497- var modifiedGroup = group.copy(lastOpenedDate = currentTime)
498-
499- // If the group's parent wasn't backed up or doesn't exist
500- // then set it the parent to the root group
501- if (! groupUids.contains(group.parentUid)) {
502- modifiedGroup = modifiedGroup.copy(parentUid = null )
503- }
504-
505- val siblings =
506- groupRepository.getGroupsByParent(modifiedGroup.parentUid).first()
507-
508- modifiedGroup = RepositoryUtils .saveUniqueName(
509- modifiedGroup,
510- saveBlock = { renamedGroup ->
511- // Do not rename the group with a (1) if it is the same UID. Just overwrite the name.
512- if (siblings.any { sibling -> sibling.uid != renamedGroup.uid && sibling.name == renamedGroup.name }) {
513- throw IllegalStateException (" Non unique group name" )
514- }
515- },
516- renameBlock = { entity, suffix ->
517- entity.copy(name = " ${entity.name} $suffix " )
518- },
519- )
520-
521- if (existingGroupUids.contains(modifiedGroup.uid)) {
522- groupRepository.update(modifiedGroup)
523- } else {
524- groupRepository.insert(modifiedGroup)
480+ for (tree in groupRestoreTrees) {
481+ tree.breadFirstTraversal { group ->
482+ restoreGroup(group, currentTime, groupUids, existingGroupUids)
525483 }
526484 }
527485 }
@@ -615,6 +573,91 @@ class BackupManagerImpl(
615573 }
616574 }
617575
576+ private suspend fun restoreGroup (
577+ group : GroupEntity ,
578+ currentTime : Long ,
579+ groupUids : Set <String >,
580+ existingGroupUids : Set <String >,
581+ ) {
582+ // Set the last opened date to now so that the imported group
583+ // shows as the most recent.
584+ var modifiedGroup = group.copy(lastOpenedDate = currentTime)
585+
586+ // If the group's parent wasn't backed up or doesn't exist
587+ // then set it the parent to the root group
588+ if (! groupUids.contains(group.parentUid)) {
589+ modifiedGroup = modifiedGroup.copy(parentUid = null )
590+ }
591+
592+ val siblings =
593+ groupRepository.getGroupsByParent(modifiedGroup.parentUid).first()
594+
595+ modifiedGroup = RepositoryUtils .saveUniqueName(
596+ modifiedGroup,
597+ saveBlock = { renamedGroup ->
598+ // Do not rename the group with a (1) if it is the same UID. Just overwrite the name.
599+ if (siblings.any { sibling -> sibling.uid != renamedGroup.uid && sibling.name == renamedGroup.name }) {
600+ throw IllegalStateException (" Non unique group name" )
601+ }
602+ },
603+ renameBlock = { entity, suffix ->
604+ entity.copy(name = " ${entity.name} $suffix " )
605+ },
606+ )
607+
608+ if (existingGroupUids.contains(modifiedGroup.uid)) {
609+ groupRepository.update(modifiedGroup)
610+ } else {
611+ groupRepository.insert(modifiedGroup)
612+ }
613+ }
614+
615+ /* *
616+ * Converts the group relationships into trees. This first finds all the root groups which
617+ * have no parent. Then it loops over all the other groups indefinitely until they have been
618+ * added to their parent. If the parent does not exist while looping then it is skipped and
619+ * processed in the next iteration.
620+ *
621+ * @return A list of the root nodes for all the group trees.
622+ */
623+ private fun buildGroupTrees (groups : List <GroupEntity >): List <TreeNode <GroupEntity >> {
624+ if (groups.isEmpty()) {
625+ return emptyList()
626+ }
627+
628+ val nodeMap = mutableMapOf<String , TreeNode <GroupEntity >>()
629+ val rootNodes = mutableListOf<TreeNode <GroupEntity >>()
630+
631+ val groupQueue = LinkedList <GroupEntity >()
632+
633+ for (group in groups) {
634+ if (group.parentUid == null ) {
635+ val node = TreeNode (group)
636+ nodeMap[group.uid] = node
637+ rootNodes.add(node)
638+ } else {
639+ groupQueue.add(group)
640+ }
641+ }
642+
643+ while (groupQueue.isNotEmpty()) {
644+ val groupsToRemove = mutableListOf<GroupEntity >()
645+
646+ for (group in groupQueue) {
647+ if (nodeMap.containsKey(group.parentUid)) {
648+ val node = TreeNode (group)
649+ nodeMap[group.uid] = node
650+ nodeMap[group.parentUid]!! .children.add(node)
651+ groupsToRemove.add(group)
652+ }
653+ }
654+
655+ groupQueue.removeAll(groupsToRemove.toSet())
656+ }
657+
658+ return rootNodes
659+ }
660+
618661 private suspend fun appendKeyMapsInRepository (keyMaps : List <KeyMapEntity >) = withContext(dispatchers.default()) {
619662 val randomUids = keyMaps.map { it.copy(uid = UUID .randomUUID().toString()) }
620663 keyMapRepository.insert(* randomUids.toTypedArray())
0 commit comments