--- /dev/null
+package File::KDBX::Group;
+# ABSTRACT: A KDBX database group
+
+use warnings;
+use strict;
+
+use Devel::GlobalDestruction;
+use File::KDBX::Constants qw(:icon);
+use File::KDBX::Error;
+use File::KDBX::Util qw(generate_uuid);
+use List::Util qw(sum0);
+use Ref::Util qw(is_ref);
+use Scalar::Util qw(blessed);
+use Time::Piece;
+use boolean;
+use namespace::clean;
+
+use parent 'File::KDBX::Object';
+
+our $VERSION = '999.999'; # VERSION
+
+my @ATTRS = qw(uuid custom_data entries groups);
+my %ATTRS = (
+ # uuid => sub { generate_uuid(printable => 1) },
+ name => '',
+ notes => '',
+ tags => '',
+ icon_id => ICON_FOLDER,
+ custom_icon_uuid => undef,
+ is_expanded => false,
+ default_auto_type_sequence => '',
+ enable_auto_type => undef,
+ enable_searching => undef,
+ last_top_visible_entry => undef,
+ # custom_data => sub { +{} },
+ previous_parent_group => undef,
+ # entries => sub { +[] },
+ # groups => sub { +[] },
+);
+my %ATTRS_TIMES = (
+ last_modification_time => sub { gmtime },
+ creation_time => sub { gmtime },
+ last_access_time => sub { gmtime },
+ expiry_time => sub { gmtime },
+ expires => false,
+ usage_count => 0,
+ location_changed => sub { gmtime },
+);
+
+while (my ($attr, $default) = each %ATTRS) {
+ no strict 'refs'; ## no critic (ProhibitNoStrict)
+ *{$attr} = sub {
+ my $self = shift;
+ $self->{$attr} = shift if @_;
+ $self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
+ };
+}
+while (my ($attr, $default) = each %ATTRS_TIMES) {
+ no strict 'refs'; ## no critic (ProhibitNoStrict)
+ *{$attr} = sub {
+ my $self = shift;
+ $self->{times}{$attr} = shift if @_;
+ $self->{times}{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
+ };
+}
+
+sub _set_default_attributes {
+ my $self = shift;
+ $self->$_ for @ATTRS, keys %ATTRS, keys %ATTRS_TIMES;
+}
+
+sub uuid {
+ my $self = shift;
+ if (@_ || !defined $self->{uuid}) {
+ my %args = @_ % 2 == 1 ? (uuid => shift, @_) : @_;
+ my $old_uuid = $self->{uuid};
+ my $uuid = $self->{uuid} = delete $args{uuid} // generate_uuid;
+ # if (defined $old_uuid and my $kdbx = $KDBX{refaddr($self)}) {
+ # $kdbx->_update_group_uuid($old_uuid, $uuid, $self);
+ # }
+ }
+ $self->{uuid};
+}
+
+sub label { shift->name(@_) }
+
+sub entries {
+ my $self = shift;
+ my $entries = $self->{entries} //= [];
+ require File::KDBX::Entry;
+ @$entries = map { File::KDBX::Entry->wrap($_, $self->kdbx) } @$entries;
+ return $entries;
+}
+
+sub groups {
+ my $self = shift;
+ my $groups = $self->{groups} //= [];
+ @$groups = map { File::KDBX::Group->wrap($_, $self->kdbx) } @$groups;
+ return $groups;
+}
+
+sub _kpx_groups { shift->groups(@_) }
+
+sub all_groups {
+ my $self = shift;
+ return $self->kdbx->all_groups(base => $self, include_base => false);
+}
+
+sub all_entries {
+ my $self = shift;
+ return $self->kdbx->all_entries(base => $self);
+}
+
+sub _group {
+ my $self = shift;
+ my $group = shift;
+ return File::KDBX::Group->wrap($group, $self);
+}
+
+sub _entry {
+ my $self = shift;
+ my $entry = shift;
+ require File::KDBX::Entry;
+ return File::KDBX::Entry->wrap($entry, $self);
+}
+
+sub add_entry {
+ my $self = shift;
+ my $entry = shift;
+ push @{$self->{entries} ||= []}, $entry;
+ return $entry;
+}
+
+sub add_group {
+ my $self = shift;
+ my $group = shift;
+ push @{$self->{groups} ||= []}, $group;
+ return $group;
+}
+
+sub add_object {
+ my $self = shift;
+ my $obj = shift;
+ if ($obj->isa('File::KDBX::Entry')) {
+ $self->add_entry($obj);
+ }
+ elsif ($obj->isa('File::KDBX::Group')) {
+ $self->add_group($obj);
+ }
+}
+
+sub remove_object {
+ my $self = shift;
+ my $object = shift;
+ my $blessed = blessed($object);
+ return $self->remove_group($object, @_) if $blessed && $object->isa('File::KDBX::Group');
+ return $self->remove_entry($object, @_) if $blessed && $object->isa('File::KDBX::Entry');
+ return $self->remove_group($object, @_) || $self->remove_entry($object, @_);
+}
+
+sub remove_group {
+ my $self = shift;
+ my $uuid = is_ref($_[0]) ? $self->_group(shift)->uuid : shift;
+ my $objects = $self->{groups};
+ for (my $i = 0; $i < @$objects; ++$i) {
+ my $o = $objects->[$i];
+ next if $uuid ne $o->uuid;
+ return splice @$objects, $i, 1;
+ }
+}
+
+sub remove_entry {
+ my $self = shift;
+ my $uuid = is_ref($_[0]) ? $self->_entry(shift)->uuid : shift;
+ my $objects = $self->{entries};
+ for (my $i = 0; $i < @$objects; ++$i) {
+ my $o = $objects->[$i];
+ next if $uuid ne $o->uuid;
+ return splice @$objects, $i, 1;
+ }
+}
+
+sub path {
+ my $self = shift;
+ my $lineage = $self->kdbx->trace_lineage($self) or return;
+ return join('.', map { $_->name } @$lineage);
+}
+
+sub size {
+ my $self = shift;
+ return sum0 map { $_->size } @{$self->groups}, @{$self->entries};
+}
+
+sub level { $_[0]->kdbx->group_level($_[0]) }
+
+sub TO_JSON { +{%{$_[0]}} }
+
+1;
+__END__
+
+=head1 DESCRIPTION
+
+=attr uuid
+
+=attr name
+
+=attr notes
+
+=attr tags
+
+=attr icon_id
+
+=attr custom_icon_uuid
+
+=attr is_expanded
+
+=attr default_auto_type_sequence
+
+=attr enable_auto_type
+
+=attr enable_searching
+
+=attr last_top_visible_entry
+
+=attr custom_data
+
+=attr previous_parent_group
+
+=attr entries
+
+=attr groups
+
+=attr last_modification_time
+
+=attr creation_time
+
+=attr last_access_time
+
+=attr expiry_time
+
+=attr expires
+
+=attr usage_count
+
+=attr location_changed
+
+Get or set various group fields.
+
+=cut