File-KDBX
view release on metacpan or search on metacpan
lib/File/KDBX/Object.pm view on Meta::CPAN
package File::KDBX::Object;
# ABSTRACT: A KDBX database object
use warnings;
use strict;
use Devel::GlobalDestruction;
use File::KDBX::Constants qw(:bool);
use File::KDBX::Error;
use File::KDBX::Util qw(:uuid);
use Hash::Util::FieldHash qw(fieldhashes);
use List::Util qw(any first);
use Ref::Util qw(is_arrayref is_plain_arrayref is_plain_hashref is_ref);
use Scalar::Util qw(blessed weaken);
use namespace::clean;
our $VERSION = '0.906'; # VERSION
fieldhashes \my (%KDBX, %PARENT, %TXNS, %REFS, %SIGNALS);
sub new {
my $class = shift;
# copy constructor
return $_[0]->clone if @_ == 1 && blessed $_[0] && $_[0]->isa($class);
my $data;
$data = shift if is_plain_hashref($_[0]);
my $kdbx;
$kdbx = shift if @_ % 2 == 1;
my %args = @_;
$args{kdbx} //= $kdbx if defined $kdbx;
my $self = bless $data // {}, $class;
$self->init(%args);
$self->_set_nonlazy_attributes if !$data;
return $self;
}
sub _set_nonlazy_attributes { die 'Not implemented' }
sub init {
my $self = shift;
my %args = @_;
while (my ($key, $val) = each %args) {
if (my $method = $self->can($key)) {
$self->$method($val);
}
}
return $self;
}
sub wrap {
my $class = shift;
my $object = shift;
return $object if blessed $object && $object->isa($class);
return $class->new(@_, @$object) if is_arrayref($object);
return $class->new($object, @_);
}
sub label { die 'Not implemented' }
my %CLONE = (entries => 1, groups => 1, history => 1);
sub clone {
my $self = shift;
lib/File/KDBX/Object.pm view on Meta::CPAN
my $self = shift;
my $cloning = shift;
my $copy = {%$self};
delete $copy->{entries} if !$CLONE{entries};
delete $copy->{groups} if !$CLONE{groups};
delete $copy->{history} if !$CLONE{history};
return ($cloning ? Hash::Util::FieldHash::id($self) : ''), $copy;
}
sub STORABLE_thaw {
my $self = shift;
my $cloning = shift;
my $addr = shift;
my $copy = shift;
@$self{keys %$copy} = values %$copy;
if ($cloning) {
my $kdbx = $KDBX{$addr};
$self->kdbx($kdbx) if $kdbx;
}
if (defined $self->{uuid}) {
if (($CLONE{reference_password} || $CLONE{reference_username}) && $self->can('strings')) {
my $uuid = format_uuid($self->{uuid});
my $clone_obj = do {
local $CLONE{new_uuid} = 0;
local $CLONE{entries} = 1;
local $CLONE{groups} = 1;
local $CLONE{history} = 1;
local $CLONE{reference_password} = 0;
local $CLONE{reference_username} = 0;
# Clone only the entry's data and manually bless to avoid infinite recursion.
bless Storable::dclone({%$copy}), 'File::KDBX::Entry';
};
my $txn = $self->begin_work(snapshot => $clone_obj);
if ($CLONE{reference_password}) {
$self->password("{REF:P\@I:$uuid}");
}
if ($CLONE{reference_username}) {
$self->username("{REF:U\@I:$uuid}");
}
$txn->commit;
}
$self->uuid(generate_uuid) if $CLONE{new_uuid};
}
# Dualvars aren't cloned as dualvars, so dualify the icon.
$self->icon_id($self->{icon_id}) if defined $self->{icon_id};
}
sub kdbx {
my $self = shift;
$self = $self->new if !ref $self;
if (@_) {
if (my $kdbx = shift) {
$KDBX{$self} = $kdbx;
weaken $KDBX{$self};
}
else {
delete $KDBX{$self};
}
}
$KDBX{$self} or throw 'Object is disconnected', object => $self;
}
sub is_connected {
my $self = shift;
return !!eval { $self->kdbx };
}
sub id { format_uuid(shift->uuid, @_) }
sub group {
my $self = shift;
if (my $new_group = shift) {
my $old_group = $self->group;
return $new_group if Hash::Util::FieldHash::id($old_group) == Hash::Util::FieldHash::id($new_group);
# move to a new parent
$self->remove(signal => 0) if $old_group;
$self->location_changed('now');
$new_group->add_object($self);
}
my $id = Hash::Util::FieldHash::id($self);
if (my $group = $PARENT{$self}) {
my $method = $self->_parent_container;
return $group if first { $id == Hash::Util::FieldHash::id($_) } @{$group->$method};
delete $PARENT{$self};
}
# always get lineage from root to leaf because the other way requires parent, so it would be recursive
my $lineage = $self->kdbx->_trace_lineage($self) or return;
my $group = pop @$lineage or return;
$PARENT{$self} = $group; weaken $PARENT{$self};
return $group;
}
sub _set_group {
my $self = shift;
if (my $parent = shift) {
$PARENT{$self} = $parent;
weaken $PARENT{$self};
}
else {
delete $PARENT{$self};
}
return $self;
}
### Name of the parent attribute expected to contain the object
sub _parent_container { die 'Not implemented' }
sub lineage {
my $self = shift;
my $base = shift;
my $base_addr = $base ? Hash::Util::FieldHash::id($base) : 0;
# try leaf to root
my @path;
my $object = $self;
while ($object = $object->group) {
unshift @path, $object;
last if $base_addr == Hash::Util::FieldHash::id($object);
}
return \@path if @path && ($base_addr == Hash::Util::FieldHash::id($path[0]) || $path[0]->is_root);
# try root to leaf
return $self->kdbx->_trace_lineage($self, $base);
}
sub remove {
my $self = shift;
my $parent = $self->group;
$parent->remove_object($self, @_) if $parent;
$self->_set_group(undef);
return $self;
}
sub recycle {
my $self = shift;
return $self->group($self->kdbx->recycle_bin);
}
sub recycle_or_remove {
my $self = shift;
my $kdbx = eval { $self->kdbx };
if ($kdbx && $kdbx->recycle_bin_enabled && !$self->is_recycled) {
$self->recycle;
}
else {
$self->remove;
}
}
sub is_recycled {
my $self = shift;
( run in 1.794 second using v1.01-cache-2.11-cpan-39bf76dae61 )