1 package File
::KDBX
::Group
;
2 # ABSTRACT: A KDBX database group
7 use Devel
::GlobalDestruction
;
8 use File
::KDBX
::Constants
qw(:icon);
10 use File
::KDBX
::Util
qw(generate_uuid);
11 use List
::Util
qw(sum0);
12 use Ref
::Util
qw(is_ref);
13 use Scalar
::Util
qw(blessed refaddr);
18 use parent
'File::KDBX::Object';
20 our $VERSION = '999.999'; # VERSION
22 sub _parent_container
{ 'groups' }
24 my @ATTRS = qw(uuid custom_data entries groups);
26 # uuid => sub { generate_uuid(printable => 1) },
30 icon_id
=> ICON_FOLDER
,
31 custom_icon_uuid
=> undef,
33 default_auto_type_sequence
=> '',
34 enable_auto_type
=> undef,
35 enable_searching
=> undef,
36 last_top_visible_entry
=> undef,
37 # custom_data => sub { +{} },
38 previous_parent_group
=> undef,
39 # entries => sub { +[] },
40 # groups => sub { +[] },
43 last_modification_time
=> sub { gmtime },
44 creation_time
=> sub { gmtime },
45 last_access_time
=> sub { gmtime },
46 expiry_time
=> sub { gmtime },
49 location_changed
=> sub { gmtime },
52 while (my ($attr, $default) = each %ATTRS) {
53 no strict
'refs'; ## no critic (ProhibitNoStrict)
56 $self->{$attr} = shift if @_;
57 $self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
60 while (my ($attr, $default) = each %ATTRS_TIMES) {
61 no strict
'refs'; ## no critic (ProhibitNoStrict)
64 $self->{times}{$attr} = shift if @_;
65 $self->{times}{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
69 sub _set_default_attributes
{
71 $self->$_ for @ATTRS, keys %ATTRS, keys %ATTRS_TIMES;
76 if (@_ || !defined $self->{uuid
}) {
77 my %args = @_ % 2 == 1 ? (uuid
=> shift, @_) : @_;
78 my $old_uuid = $self->{uuid
};
79 my $uuid = $self->{uuid
} = delete $args{uuid
} // generate_uuid
;
80 # if (defined $old_uuid and my $kdbx = $KDBX{refaddr($self)}) {
81 # $kdbx->_update_group_uuid($old_uuid, $uuid, $self);
87 ##############################################################################
91 my $entries = $self->{entries
} //= [];
92 # FIXME - Looping through entries on each access is too expensive.
93 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
99 # FIXME - shouldn't have to delegate to the database to get this
100 return $self->kdbx->all_entries(base
=> $self);
105 $entry = $group->add_entry($entry);
106 $entry = $group->add_entry(%entry_attributes);
108 Add an entry to a group
. If C
<$entry> already
has a parent group
, it will be removed from that group before
109 being added to C
<$group>.
115 my $entry = @_ % 2 == 1 ? shift : undef;
118 my $kdbx = delete $args{kdbx
} // eval { $self->kdbx };
120 $entry = $self->_wrap_entry($entry // [%args]);
122 $entry->kdbx($kdbx) if $kdbx;
124 push @{$self->{entries
} ||= []}, $entry->remove;
125 return $entry->_set_group($self);
130 my $uuid = is_ref
($_[0]) ? $self->_wrap_entry(shift)->uuid : shift;
131 my $objects = $self->{entries
};
132 for (my $i = 0; $i < @$objects; ++$i) {
133 my $o = $objects->[$i];
134 next if $uuid ne $o->uuid;
135 return splice @$objects, $i, 1;
136 $o->_set_group(undef);
137 return @$objects, $i, 1;
141 ##############################################################################
145 my $groups = $self->{groups
} //= [];
146 # FIXME - Looping through groups on each access is too expensive.
147 @$groups = map { $self->_wrap_group($_, $self->kdbx) } @$groups;
153 # FIXME - shouldn't have to delegate to the database to get this
154 return $self->kdbx->all_groups(base
=> $self, include_base
=> false
);
157 sub _kpx_groups
{ shift-
>groups(@_) }
161 $new_group = $group->add_group($new_group);
162 $new_group = $group->add_group(%group_attributes);
164 Add a group to a group
. If C
<$new_group> already
has a parent group
, it will be removed from that group before
165 being added to C
<$group>.
171 my $group = @_ % 2 == 1 ? shift : undef;
174 my $kdbx = delete $args{kdbx
} // eval { $self->kdbx };
176 $group = $self->_wrap_group($group // [%args]);
178 $group->kdbx($kdbx) if $kdbx;
180 push @{$self->{groups
} ||= []}, $group->remove;
181 return $group->_set_group($self);
186 my $uuid = is_ref
($_[0]) ? $self->_wrap_group(shift)->uuid : shift;
187 my $objects = $self->{groups
};
188 for (my $i = 0; $i < @$objects; ++$i) {
189 my $o = $objects->[$i];
190 next if $uuid ne $o->uuid;
191 $o->_set_group(undef);
192 return splice @$objects, $i, 1;
196 ##############################################################################
200 $new_entry = $group->add_object($new_entry);
201 $new_group = $group->add_object($new_group);
203 Add an object
(either a L
<File
::KDBX
::Entry
> or a L
<File
::KDBX
::Group
>) to a group
. This
is the generic
204 equivalent of the object forms of L
</add_entry> and L</add_group
>.
211 if ($obj->isa('File::KDBX::Entry')) {
212 $self->add_entry($obj);
214 elsif ($obj->isa('File::KDBX::Group')) {
215 $self->add_group($obj);
219 =method remove_object
221 $group->remove_object($entry);
222 $group->remove_object($group);
224 Remove an object
(either a L
<File
::KDBX
::Entry
> or a L
<File
::KDBX
::Group
>) from a group
. This
is the generic
225 equivalent of the object forms of L
</remove_entry> and L</remove_group
>.
232 my $blessed = blessed
($object);
233 return $self->remove_group($object, @_) if $blessed && $object->isa('File::KDBX::Group');
234 return $self->remove_entry($object, @_) if $blessed && $object->isa('File::KDBX::Entry');
235 return $self->remove_group($object, @_) || $self->remove_entry($object, @_);
238 ##############################################################################
242 $bool = $group->is_root;
244 Determine
if a group
is the root group of its associated database
.
250 my $kdbx = eval { $self->kdbx } or return;
251 return refaddr
($kdbx->root) == refaddr
($self);
256 $string = $group->path;
258 Get a string representation of a group
's lineage. This is used as the substitution value for the
259 C<{GROUP_PATH}> placeholder. See L<File::KDBX::Entry/Placeholders>.
261 For a root group, the path is simply the name of the group. For deeper groups, the path is a period-separated
262 sequence of group names between the root group and C<$group>, including C<$group> but I<not> the root group.
263 In other words, paths of deeper groups leave the root group name out.
266 -> Root # path is "Root"
267 -> Foo # path is "Foo"
268 -> Bar # path is "Foo.Bar"
270 Yeah, it doesn't make much sense to me
, either
, but this matches the behavior of KeePass
.
276 return $self->name if $self->is_root;
277 my $lineage = $self->lineage or return;
278 my @parts = (@$lineage, $self);
280 return join('.', map { $_->name } @parts);
285 $size = $group->size;
287 Get the size
(in bytes
) of a group
, including the size of all subroups
and entries
, if any
.
293 return sum0
map { $_->size } @{$self->groups}, @{$self->entries};
298 $depth = $group->depth;
300 Get the depth of a group within a database
. The root group
is at depth
0, its direct children are at depth
1,
301 etc
. A group
not in a database tree structure returns a depth of
-1.
305 sub depth
{ $_[0]->is_root ? 0 : (scalar @{$_[0]->lineage || []} || -1) }
307 sub label
{ shift-
>name(@_) }
324 =attr custom_icon_uuid
328 =attr default_auto_type_sequence
330 =attr enable_auto_type
332 =attr enable_searching
334 =attr last_top_visible_entry
338 =attr previous_parent_group
344 =attr last_modification_time
348 =attr last_access_time
356 =attr location_changed
358 Get or set various group fields.