2024-12-04 19:32:09 -03:00

103 lines
3.1 KiB
Python

"""Folder management functionality for Bitwarden to KeePass conversion.
Provides classes and functions for managing folder hierarchies when converting
from Bitwarden's folder structure to KeePass groups.
"""
import collections
from typing import Callable, Deque, List, Optional
from pykeepass.group import Group as KPGroup
class Folder:
"""Represents a folder in the Bitwarden/KeePass hierarchy.
Attributes:
id: Bitwarden folder identifier
name: Display name of the folder
children: List of child folders
parent: Parent folder reference
keepass_group: Associated KeePass group object
"""
id: Optional[str]
name: Optional[str]
children: List["Folder"]
parent: Optional["Folder"]
keepass_group: Optional[KPGroup]
def __init__(self, id_: Optional[str]):
"""Initialize a new folder.
Args:
folder_id: Bitwarden folder identifier
"""
self.id = id_
self.name = None
self.children = []
self.parent = None
self.keepass_group = None
def add_child(self, child: "Folder"):
"""Add a child folder to this folder.
Args:
child: Folder object to add as a child
"""
self.children.append(child)
child.parent = self
# logic was lifted directly from https://github.com/bitwarden/jslib/blob/ecdd08624f61ccff8128b7cb3241f39e664e1c7f/common/src/misc/serviceUtils.ts#L7
def nested_traverse_insert(root: Folder, name_parts: List[str], new_folder: Folder, delimiter: str) -> None:
"""Insert a new folder into the folder hierarchy.
Args:
root: Root folder to start traversal from
name_parts: Path components for the new folder
new_folder: Folder object to insert
delimiter: Character used to separate path components
"""
if len(name_parts) == 0:
return
end: bool = len(name_parts) == 1
part_name: str = name_parts[0]
for child in root.children:
if child.name != part_name:
continue
if end and child.id != new_folder.id:
# Another node with the same name.
new_folder.name = part_name
root.add_child(new_folder)
return
nested_traverse_insert(child, name_parts[1:], new_folder, delimiter)
return
if end:
new_folder.name = part_name
root.add_child(new_folder)
return
new_part_name: str = part_name + delimiter + name_parts[1]
new_name_parts: List[str] = [new_part_name]
new_name_parts.extend(name_parts[2:])
nested_traverse_insert(root, new_name_parts, new_folder, delimiter)
def bfs_traverse_execute(root: Folder, callback: Callable[[Folder], None]) -> None:
"""Execute a callback on each folder in breadth-first order.
Args:
root: Root folder to start traversal from
callback: Function to execute on each folder
"""
queue: Deque[Folder] = collections.deque()
queue.extend(root.children)
while queue:
child: Folder = queue.popleft()
queue.extend(child.children)
callback(child)