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(:class :coercion generate_uuid);
11 use Hash
::Util
::FieldHash
;
12 use List
::Util
qw(sum0);
13 use Ref
::Util
qw(is_coderef is_ref);
14 use Scalar
::Util
qw(blessed);
19 extends
'File::KDBX::Object';
21 our $VERSION = '999.999'; # VERSION
23 sub _parent_container
{ 'groups' }
25 # has uuid => sub { generate_uuid(printable => 1) };
26 has name
=> '', coerce
=> \
&to_string
;
27 has notes
=> '', coerce
=> \
&to_string
;
28 has tags
=> '', coerce
=> \
&to_string
;
29 has icon_id
=> ICON_FOLDER
, coerce
=> \
&to_icon_constant
;
30 has custom_icon_uuid
=> undef, coerce
=> \
&to_uuid
;
31 has is_expanded
=> false
, coerce
=> \
&to_bool
;
32 has default_auto_type_sequence
=> '', coerce
=> \
&to_string
;
33 has enable_auto_type
=> undef, coerce
=> \
&to_tristate
;
34 has enable_searching
=> undef, coerce
=> \
&to_tristate
;
35 has last_top_visible_entry
=> undef, coerce
=> \
&to_uuid
;
36 # has custom_data => {};
37 has previous_parent_group
=> undef, coerce
=> \
&to_uuid
;
42 has last_modification_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
43 has creation_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
44 has last_access_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
45 has expiry_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
46 has expires
=> false
, store
=> 'times', coerce
=> \
&to_bool
;
47 has usage_count
=> 0, store
=> 'times', coerce
=> \
&to_number
;
48 has location_changed
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
50 my @ATTRS = qw(uuid custom_data entries groups);
51 sub _set_nonlazy_attributes
{
53 $self->$_ for @ATTRS, list_attributes
(ref $self);
58 if (@_ || !defined $self->{uuid
}) {
59 my %args = @_ % 2 == 1 ? (uuid
=> shift, @_) : @_;
60 my $old_uuid = $self->{uuid
};
61 my $uuid = $self->{uuid
} = delete $args{uuid
} // generate_uuid
;
62 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid;
67 ##############################################################################
71 my $entries = $self->{entries
} //= [];
72 # FIXME - Looping through entries on each access is too expensive.
73 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
79 # FIXME - shouldn't have to delegate to the database to get this
80 return $self->kdbx->all_entries(base
=> $self);
85 $entry = $group->add_entry($entry);
86 $entry = $group->add_entry(%entry_attributes);
88 Add an entry to a group
. If C
<$entry> already
has a parent group
, it will be removed from that group before
89 being added to C
<$group>.
95 my $entry = @_ % 2 == 1 ? shift : undef;
98 my $kdbx = delete $args{kdbx
} // eval { $self->kdbx };
100 $entry = $self->_wrap_entry($entry // [%args]);
102 $entry->kdbx($kdbx) if $kdbx;
104 push @{$self->{entries
} ||= []}, $entry->remove;
105 return $entry->_set_group($self)->_signal('added', $self);
110 my $uuid = is_ref
($_[0]) ? $self->_wrap_entry(shift)->uuid : shift;
111 my $objects = $self->{entries
};
112 for (my $i = 0; $i < @$objects; ++$i) {
113 my $o = $objects->[$i];
114 next if $uuid ne $o->uuid;
115 $o->_set_group(undef)->_signal('removed');
116 return splice @$objects, $i, 1;
120 ##############################################################################
124 my $groups = $self->{groups
} //= [];
125 # FIXME - Looping through groups on each access is too expensive.
126 @$groups = map { $self->_wrap_group($_, $self->kdbx) } @$groups;
132 \
@groups = $group->all_groups(%options);
134 Get all groups within a group
, deeply
, in a flat array
. Supported options
:
142 for my $subgroup (@{$self->groups}) {
143 push @groups, @{$subgroup->all_groups};
151 @groups = $kdbx->find_groups($query, %options);
153 Find all groups deeply that match to a query
. Options are the same as
for L
</all_groups
>.
155 See L
</QUERY
> for a description of what C
<$query> can be
.
161 my $query = shift or throw
'Must provide a query';
163 my %all_groups = ( # FIXME
165 include_base
=> $args{include_base
},
167 return @{search
($self->all_groups(%all_groups), is_arrayref
($query) ? @$query : $query)};
170 sub _kpx_groups
{ shift-
>groups(@_) }
174 $new_group = $group->add_group($new_group);
175 $new_group = $group->add_group(%group_attributes);
177 Add a group to a group
. If C
<$new_group> already
has a parent group
, it will be removed from that group before
178 being added to C
<$group>.
184 my $group = @_ % 2 == 1 ? shift : undef;
187 my $kdbx = delete $args{kdbx
} // eval { $self->kdbx };
189 $group = $self->_wrap_group($group // [%args]);
191 $group->kdbx($kdbx) if $kdbx;
193 push @{$self->{groups
} ||= []}, $group->remove;
194 return $group->_set_group($self)->_signal('added', $self);
199 my $uuid = is_ref
($_[0]) ? $self->_wrap_group(shift)->uuid : shift;
200 my $objects = $self->{groups
};
201 for (my $i = 0; $i < @$objects; ++$i) {
202 my $o = $objects->[$i];
203 next if $uuid ne $o->uuid;
204 $o->_set_group(undef)->_signal('removed');
205 return splice @$objects, $i, 1;
209 ##############################################################################
213 $new_entry = $group->add_object($new_entry);
214 $new_group = $group->add_object($new_group);
216 Add an object
(either a L
<File
::KDBX
::Entry
> or a L
<File
::KDBX
::Group
>) to a group
. This
is the generic
217 equivalent of the object forms of L
</add_entry> and L</add_group
>.
224 if ($obj->isa('File::KDBX::Entry')) {
225 $self->add_entry($obj);
227 elsif ($obj->isa('File::KDBX::Group')) {
228 $self->add_group($obj);
232 =method remove_object
234 $group->remove_object($entry);
235 $group->remove_object($group);
237 Remove an object
(either a L
<File
::KDBX
::Entry
> or a L
<File
::KDBX
::Group
>) from a group
. This
is the generic
238 equivalent of the object forms of L
</remove_entry> and L</remove_group
>.
245 my $blessed = blessed
($object);
246 return $self->remove_group($object, @_) if $blessed && $object->isa('File::KDBX::Group');
247 return $self->remove_entry($object, @_) if $blessed && $object->isa('File::KDBX::Entry');
248 return $self->remove_group($object, @_) || $self->remove_entry($object, @_);
251 ##############################################################################
255 $bool = $group->is_root;
257 Determine
if a group
is the root group of its associated database
.
263 my $kdbx = eval { $self->kdbx } or return;
264 return Hash
::Util
::FieldHash
::id
($kdbx->root) == Hash
::Util
::FieldHash
::id
($self);
269 $string = $group->path;
271 Get a string representation of a group
's lineage. This is used as the substitution value for the
272 C<{GROUP_PATH}> placeholder. See L<File::KDBX::Entry/Placeholders>.
274 For a root group, the path is simply the name of the group. For deeper groups, the path is a period-separated
275 sequence of group names between the root group and C<$group>, including C<$group> but I<not> the root group.
276 In other words, paths of deeper groups leave the root group name out.
279 -> Root # path is "Root"
280 -> Foo # path is "Foo"
281 -> Bar # path is "Foo.Bar"
283 Yeah, it doesn't make much sense to me
, either
, but this matches the behavior of KeePass
.
289 return $self->name if $self->is_root;
290 my $lineage = $self->lineage or return;
291 my @parts = (@$lineage, $self);
293 return join('.', map { $_->name } @parts);
298 $size = $group->size;
300 Get the size
(in bytes
) of a group
, including the size of all subroups
and entries
, if any
.
306 return sum0
map { $_->size } @{$self->groups}, @{$self->entries};
311 $depth = $group->depth;
313 Get the depth of a group within a database
. The root group
is at depth
0, its direct children are at depth
1,
314 etc
. A group
not in a database tree structure returns a depth of
-1.
318 sub depth
{ $_[0]->is_root ? 0 : (scalar @{$_[0]->lineage || []} || -1) }
320 sub label
{ shift-
>name(@_) }
325 return $self->SUPER::_signal
("group.$type", @_);
331 $self->last_modification_time($time);
332 $self->last_access_time($time);
350 =attr custom_icon_uuid
354 =attr default_auto_type_sequence
356 =attr enable_auto_type
358 =attr enable_searching
360 =attr last_top_visible_entry
364 =attr previous_parent_group
370 =attr last_modification_time
374 =attr last_access_time
382 =attr location_changed
384 Get or set various group fields.