+# sub check {
+# - Fixer tool. Can repair inconsistencies, including:
+# - Orphaned binaries... not really a thing anymore since we now distribute binaries amongst entries
+# - Unused custom icons (OFF, data loss)
+# - Duplicate icons
+# - All data types are valid
+# - date times are correct
+# - boolean fields
+# - All UUIDs refer to things that exist
+# - previous parent group
+# - recycle bin
+# - last selected group
+# - last visible group
+# - Enforce history size limits (ON)
+# - Check headers/meta (ON)
+# - Duplicate deleted objects (ON)
+# - Duplicate window associations (OFF)
+# - Header UUIDs match known ciphers/KDFs?
+# }
+
+=method remove_empty_groups
+
+ $kdbx->remove_empty_groups;
+
+Remove groups with no subgroups and no entries.
+
+=cut
+
+sub remove_empty_groups {
+ my $self = shift;
+ my @removed;
+ $self->groups(algorithm => 'dfs')
+ ->where(-true => 'is_empty')
+ ->each(sub { push @removed, $_->remove });
+ return @removed;
+}
+
+=method remove_unused_icons
+
+ $kdbx->remove_unused_icons;
+
+Remove icons that are not associated with any entry or group in the database.
+
+=cut
+
+sub remove_unused_icons {
+ my $self = shift;
+ my %icons = map { $_->{uuid} => 0 } @{$self->custom_icons};
+
+ $self->objects->each(sub { ++$icons{$_->custom_icon_uuid // ''} });
+
+ my @removed;
+ push @removed, $self->remove_custom_icon($_) for grep { $icons{$_} == 0 } keys %icons;
+ return @removed;
+}
+
+=method remove_duplicate_icons
+
+ $kdbx->remove_duplicate_icons;
+
+Remove duplicate icons as determined by hashing the icon data.
+
+=cut
+
+sub remove_duplicate_icons {
+ my $self = shift;
+
+ my %seen;
+ my %dup;
+ for my $icon (@{$self->custom_icons}) {
+ my $digest = digest_data('SHA256', $icon->{data});
+ if (my $other = $seen{$digest}) {
+ $dup{$icon->{uuid}} = $other->{uuid};
+ }
+ else {
+ $seen{$digest} = $icon;
+ }
+ }
+
+ my @removed;
+ while (my ($old_uuid, $new_uuid) = each %dup) {
+ $self->objects
+ ->where(custom_icon_uuid => $old_uuid)
+ ->each(sub { $_->custom_icon_uuid($new_uuid) });
+ push @removed, $self->remove_custom_icon($old_uuid);
+ }
+ return @removed;
+}
+
+=method prune_history
+
+ $kdbx->prune_history(%options);
+
+Remove just as many older historical entries as necessary to get under certain limits.
+
+=for :list
+* C<max_items> - Maximum number of historical entries to keep (default: value of L</history_max_items>, no
+ limit: -1)
+* C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: value of
+ L</history_max_size>, no limit: -1)
+* C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
+
+=cut
+
+sub prune_history {
+ my $self = shift;
+ my %args = @_;
+
+ my $max_items = $args{max_items} // $self->history_max_items // HISTORY_DEFAULT_MAX_ITEMS;
+ my $max_size = $args{max_size} // $self->history_max_size // HISTORY_DEFAULT_MAX_SIZE;
+ my $max_age = $args{max_age} // $self->maintenance_history_days // HISTORY_DEFAULT_MAX_AGE;
+
+ my @removed;
+ $self->entries->each(sub {
+ push @removed, $_->prune_history(
+ max_items => $max_items,
+ max_size => $max_size,
+ max_age => $max_age,
+ );
+ });
+ return @removed;
+}
+