mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
4a5bd002c0
The primary source for project info is now src/version.h. The pgBackRestDoc::ProjectInfo module loads the project info from src/version.h at runtime so there is no need to update it.
868 lines
30 KiB
Perl
868 lines
30 KiB
Perl
####################################################################################################################################
|
|
# COMMON INI MODULE
|
|
####################################################################################################################################
|
|
package pgBackRestDoc::Common::Ini;
|
|
|
|
use strict;
|
|
use warnings FATAL => qw(all);
|
|
use Carp qw(confess);
|
|
use English '-no_match_vars';
|
|
|
|
use Digest::SHA qw(sha1_hex);
|
|
use Exporter qw(import);
|
|
our @EXPORT = qw();
|
|
use File::Basename qw(dirname);
|
|
use JSON::PP;
|
|
use Storable qw(dclone);
|
|
|
|
use pgBackRestDoc::Common::Exception;
|
|
use pgBackRestDoc::Common::Log;
|
|
use pgBackRestDoc::Common::String;
|
|
use pgBackRestDoc::ProjectInfo;
|
|
|
|
####################################################################################################################################
|
|
# Boolean constants
|
|
####################################################################################################################################
|
|
use constant INI_TRUE => JSON::PP::true;
|
|
push @EXPORT, qw(INI_TRUE);
|
|
use constant INI_FALSE => JSON::PP::false;
|
|
push @EXPORT, qw(INI_FALSE);
|
|
|
|
####################################################################################################################################
|
|
# Ini control constants
|
|
####################################################################################################################################
|
|
use constant INI_SECTION_BACKREST => 'backrest';
|
|
push @EXPORT, qw(INI_SECTION_BACKREST);
|
|
|
|
use constant INI_KEY_CHECKSUM => 'backrest-checksum';
|
|
push @EXPORT, qw(INI_KEY_CHECKSUM);
|
|
use constant INI_KEY_FORMAT => 'backrest-format';
|
|
push @EXPORT, qw(INI_KEY_FORMAT);
|
|
use constant INI_KEY_VERSION => 'backrest-version';
|
|
push @EXPORT, qw(INI_KEY_VERSION);
|
|
|
|
use constant INI_SECTION_CIPHER => 'cipher';
|
|
push @EXPORT, qw(INI_SECTION_CIPHER);
|
|
|
|
use constant INI_KEY_CIPHER_PASS => 'cipher-pass';
|
|
push @EXPORT, qw(INI_KEY_CIPHER_PASS);
|
|
|
|
####################################################################################################################################
|
|
# Ini file copy extension
|
|
####################################################################################################################################
|
|
use constant INI_COPY_EXT => '.copy';
|
|
push @EXPORT, qw(INI_COPY_EXT);
|
|
|
|
####################################################################################################################################
|
|
# Ini sort orders
|
|
####################################################################################################################################
|
|
use constant INI_SORT_FORWARD => 'forward';
|
|
push @EXPORT, qw(INI_SORT_FORWARD);
|
|
use constant INI_SORT_REVERSE => 'reverse';
|
|
push @EXPORT, qw(INI_SORT_REVERSE);
|
|
use constant INI_SORT_NONE => 'none';
|
|
push @EXPORT, qw(INI_SORT_NONE);
|
|
|
|
####################################################################################################################################
|
|
# new()
|
|
####################################################################################################################################
|
|
sub new
|
|
{
|
|
my $class = shift; # Class name
|
|
|
|
# Create the class hash
|
|
my $self = {};
|
|
bless $self, $class;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
(
|
|
my $strOperation,
|
|
$self->{oStorage},
|
|
$self->{strFileName},
|
|
my $bLoad,
|
|
my $strContent,
|
|
$self->{iInitFormat},
|
|
$self->{strInitVersion},
|
|
my $bIgnoreMissing,
|
|
$self->{strCipherPass}, # Passphrase to read/write the file
|
|
my $strCipherPassSub, # Passphrase to read/write subsequent files
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->new', \@_,
|
|
{name => 'oStorage', trace => true},
|
|
{name => 'strFileName', trace => true},
|
|
{name => 'bLoad', optional => true, default => true, trace => true},
|
|
{name => 'strContent', optional => true, trace => true},
|
|
{name => 'iInitFormat', optional => true, default => REPOSITORY_FORMAT, trace => true},
|
|
{name => 'strInitVersion', optional => true, default => PROJECT_VERSION, trace => true},
|
|
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
|
|
{name => 'strCipherPass', optional => true, trace => true},
|
|
{name => 'strCipherPassSub', optional => true, trace => true},
|
|
);
|
|
|
|
# Set changed to false
|
|
$self->{bModified} = false;
|
|
|
|
# Set exists to false
|
|
$self->{bExists} = false;
|
|
|
|
# Load the file if requested
|
|
if ($bLoad)
|
|
{
|
|
$self->load($bIgnoreMissing);
|
|
}
|
|
# Load from a string if provided
|
|
elsif (defined($strContent))
|
|
{
|
|
$self->{oContent} = iniParse($strContent);
|
|
$self->headerCheck();
|
|
}
|
|
|
|
# Initialize if not loading the file and not loading from string or if a load was attempted and the file does not exist
|
|
if (!$self->{bExists} && !defined($strContent))
|
|
{
|
|
$self->numericSet(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, $self->{iInitFormat});
|
|
$self->set(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $self->{strInitVersion});
|
|
|
|
# Determine if the passphrase section should be set
|
|
if (defined($self->{strCipherPass}) && defined($strCipherPassSub))
|
|
{
|
|
$self->set(INI_SECTION_CIPHER, INI_KEY_CIPHER_PASS, undef, $strCipherPassSub);
|
|
}
|
|
}
|
|
|
|
return $self;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# loadVersion() - load a version (main or copy) of the ini file
|
|
####################################################################################################################################
|
|
sub loadVersion
|
|
{
|
|
my $self = shift;
|
|
my $bCopy = shift;
|
|
my $bIgnoreError = shift;
|
|
|
|
# Load main
|
|
my $rstrContent = $self->{oStorage}->get(
|
|
$self->{oStorage}->openRead($self->{strFileName} . ($bCopy ? INI_COPY_EXT : ''),
|
|
{bIgnoreMissing => $bIgnoreError, strCipherPass => $self->{strCipherPass}}));
|
|
|
|
# If the file exists then attempt to parse it
|
|
if (defined($rstrContent))
|
|
{
|
|
my $rhContent = iniParse($$rstrContent, {bIgnoreInvalid => $bIgnoreError});
|
|
|
|
# If the content is valid then check the header
|
|
if (defined($rhContent))
|
|
{
|
|
$self->{oContent} = $rhContent;
|
|
|
|
# If the header is invalid then undef content
|
|
if (!$self->headerCheck({bIgnoreInvalid => $bIgnoreError}))
|
|
{
|
|
delete($self->{oContent});
|
|
}
|
|
}
|
|
}
|
|
|
|
return defined($self->{oContent});
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# load() - load the ini
|
|
####################################################################################################################################
|
|
sub load
|
|
{
|
|
my $self = shift;
|
|
my $bIgnoreMissing = shift;
|
|
|
|
# If main was not loaded then try the copy
|
|
if (!$self->loadVersion(false, true))
|
|
{
|
|
if (!$self->loadVersion(true, true))
|
|
{
|
|
return if $bIgnoreMissing;
|
|
|
|
confess &log(ERROR, "unable to open $self->{strFileName} or $self->{strFileName}" . INI_COPY_EXT, ERROR_FILE_MISSING);
|
|
}
|
|
}
|
|
|
|
$self->{bExists} = true;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# headerCheck() - check that version and checksum in header are as expected
|
|
####################################################################################################################################
|
|
sub headerCheck
|
|
{
|
|
my $self = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$bIgnoreInvalid,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->headerCheck', \@_,
|
|
{name => 'bIgnoreInvalid', optional => true, default => false, trace => true},
|
|
);
|
|
|
|
# Eval so exceptions can be ignored on bIgnoreInvalid
|
|
my $bValid = true;
|
|
|
|
eval
|
|
{
|
|
|
|
# Make sure the ini is valid by testing checksum
|
|
my $strChecksum = $self->get(INI_SECTION_BACKREST, INI_KEY_CHECKSUM, undef, false);
|
|
my $strTestChecksum = $self->hash();
|
|
|
|
if (!defined($strChecksum) || $strChecksum ne $strTestChecksum)
|
|
{
|
|
confess &log(ERROR,
|
|
"invalid checksum in '$self->{strFileName}', expected '${strTestChecksum}' but found " .
|
|
(defined($strChecksum) ? "'${strChecksum}'" : '[undef]'),
|
|
ERROR_CHECKSUM);
|
|
}
|
|
|
|
# Make sure that the format is current, otherwise error
|
|
my $iFormat = $self->get(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, false, 0);
|
|
|
|
if ($iFormat != $self->{iInitFormat})
|
|
{
|
|
confess &log(ERROR,
|
|
"invalid format in '$self->{strFileName}', expected $self->{iInitFormat} but found ${iFormat}", ERROR_FORMAT);
|
|
}
|
|
|
|
# Check if the version has changed
|
|
if (!$self->test(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $self->{strInitVersion}))
|
|
{
|
|
$self->set(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, $self->{strInitVersion});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
or do
|
|
{
|
|
# Confess the error if it should not be ignored
|
|
if (!$bIgnoreInvalid)
|
|
{
|
|
confess $EVAL_ERROR;
|
|
}
|
|
|
|
# Return false when errors are ignored
|
|
$bValid = false;
|
|
};
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'bValid', value => $bValid, trace => true}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# iniParse() - parse from standard INI format to a hash.
|
|
####################################################################################################################################
|
|
push @EXPORT, qw(iniParse);
|
|
|
|
sub iniParse
|
|
{
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$strContent,
|
|
$bRelaxed,
|
|
$bIgnoreInvalid,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '::iniParse', \@_,
|
|
{name => 'strContent', required => false, trace => true},
|
|
{name => 'bRelaxed', optional => true, default => false, trace => true},
|
|
{name => 'bIgnoreInvalid', optional => true, default => false, trace => true},
|
|
);
|
|
|
|
# Ini content
|
|
my $oContent = undef;
|
|
my $strSection;
|
|
|
|
# Create the JSON object
|
|
my $oJSON = JSON::PP->new()->allow_nonref();
|
|
|
|
# Eval so exceptions can be ignored on bIgnoreInvalid
|
|
eval
|
|
{
|
|
# Read the INI file
|
|
foreach my $strLine (split("\n", defined($strContent) ? $strContent : ''))
|
|
{
|
|
$strLine = trim($strLine);
|
|
|
|
# Skip lines that are blank or comments
|
|
if ($strLine ne '' && $strLine !~ '^[ ]*#.*')
|
|
{
|
|
# Get the section
|
|
if (index($strLine, '[') == 0)
|
|
{
|
|
$strSection = substr($strLine, 1, length($strLine) - 2);
|
|
}
|
|
else
|
|
{
|
|
if (!defined($strSection))
|
|
{
|
|
confess &log(ERROR, "key/value pair '${strLine}' found outside of a section", ERROR_CONFIG);
|
|
}
|
|
|
|
# Get key and value
|
|
my $iIndex = index($strLine, '=');
|
|
|
|
if ($iIndex == -1)
|
|
{
|
|
confess &log(ERROR, "unable to find '=' in '${strLine}'", ERROR_CONFIG);
|
|
}
|
|
|
|
my $strKey = substr($strLine, 0, $iIndex);
|
|
my $strValue = substr($strLine, $iIndex + 1);
|
|
|
|
# If relaxed then read the value directly
|
|
if ($bRelaxed)
|
|
{
|
|
if (defined($oContent->{$strSection}{$strKey}))
|
|
{
|
|
if (ref($oContent->{$strSection}{$strKey}) ne 'ARRAY')
|
|
{
|
|
$oContent->{$strSection}{$strKey} = [$oContent->{$strSection}{$strKey}];
|
|
}
|
|
|
|
push(@{$oContent->{$strSection}{$strKey}}, $strValue);
|
|
}
|
|
else
|
|
{
|
|
$oContent->{$strSection}{$strKey} = $strValue;
|
|
}
|
|
}
|
|
# Else read the value as stricter JSON
|
|
else
|
|
{
|
|
${$oContent}{$strSection}{$strKey} = $oJSON->decode($strValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Error if the file is empty
|
|
if (!($bRelaxed || defined($oContent)))
|
|
{
|
|
confess &log(ERROR, 'no key/value pairs found', ERROR_CONFIG);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
or do
|
|
{
|
|
# Confess the error if it should not be ignored
|
|
if (!$bIgnoreInvalid)
|
|
{
|
|
confess $EVAL_ERROR;
|
|
}
|
|
|
|
# Undef content when errors are ignored
|
|
undef($oContent);
|
|
};
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'oContent', value => $oContent, trace => true}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# save() - save the file.
|
|
####################################################################################################################################
|
|
sub save
|
|
{
|
|
my $self = shift;
|
|
|
|
# Save only if modified
|
|
if ($self->{bModified})
|
|
{
|
|
# Calculate the hash
|
|
$self->hash();
|
|
|
|
# Save the file
|
|
$self->{oStorage}->put($self->{strFileName}, iniRender($self->{oContent}), {strCipherPass => $self->{strCipherPass}});
|
|
|
|
if ($self->{oStorage}->can('pathSync'))
|
|
{
|
|
$self->{oStorage}->pathSync(dirname($self->{strFileName}));
|
|
}
|
|
|
|
$self->{oStorage}->put($self->{strFileName} . INI_COPY_EXT, iniRender($self->{oContent}),
|
|
{strCipherPass => $self->{strCipherPass}});
|
|
|
|
if ($self->{oStorage}->can('pathSync'))
|
|
{
|
|
$self->{oStorage}->pathSync(dirname($self->{strFileName}));
|
|
}
|
|
|
|
$self->{bModified} = false;
|
|
|
|
# Indicate the file now exists
|
|
$self->{bExists} = true;
|
|
|
|
# File was saved
|
|
return true;
|
|
}
|
|
|
|
# File was not saved
|
|
return false;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# saveCopy - save only a copy of the file.
|
|
####################################################################################################################################
|
|
sub saveCopy
|
|
{
|
|
my $self = shift;
|
|
|
|
if ($self->{oStorage}->exists($self->{strFileName}))
|
|
{
|
|
confess &log(ASSERT, "cannot save copy only when '$self->{strFileName}' exists");
|
|
}
|
|
|
|
$self->hash();
|
|
$self->{oStorage}->put($self->{strFileName} . INI_COPY_EXT, iniRender($self->{oContent}),
|
|
{strCipherPass => $self->{strCipherPass}});
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# iniRender() - render hash to standard INI format.
|
|
####################################################################################################################################
|
|
push @EXPORT, qw(iniRender);
|
|
|
|
sub iniRender
|
|
{
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$oContent,
|
|
$bRelaxed,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '::iniRender', \@_,
|
|
{name => 'oContent', trace => true},
|
|
{name => 'bRelaxed', default => false, trace => true},
|
|
);
|
|
|
|
# Open the ini file for writing
|
|
my $strContent = '';
|
|
my $bFirst = true;
|
|
|
|
# Create the JSON object canonical so that fields are alpha ordered to pass unit tests
|
|
my $oJSON = JSON::PP->new()->canonical()->allow_nonref();
|
|
|
|
# Write the INI file
|
|
foreach my $strSection (sort(keys(%$oContent)))
|
|
{
|
|
# Add a linefeed between sections
|
|
if (!$bFirst)
|
|
{
|
|
$strContent .= "\n";
|
|
}
|
|
|
|
# Write the section
|
|
$strContent .= "[${strSection}]\n";
|
|
|
|
# Iterate through all keys in the section
|
|
foreach my $strKey (sort(keys(%{$oContent->{$strSection}})))
|
|
{
|
|
# If the value is a hash then convert it to JSON, otherwise store as is
|
|
my $strValue = ${$oContent}{$strSection}{$strKey};
|
|
|
|
# If relaxed then store as old-style config
|
|
if ($bRelaxed)
|
|
{
|
|
# If the value is an array then save each element to a separate key/value pair
|
|
if (ref($strValue) eq 'ARRAY')
|
|
{
|
|
foreach my $strArrayValue (@{$strValue})
|
|
{
|
|
$strContent .= "${strKey}=${strArrayValue}\n";
|
|
}
|
|
}
|
|
# Else write a standard key/value pair
|
|
else
|
|
{
|
|
$strContent .= "${strKey}=${strValue}\n";
|
|
}
|
|
}
|
|
# Else write as stricter JSON
|
|
else
|
|
{
|
|
# Skip the checksum for now but write all other key/value pairs
|
|
if (!($strSection eq INI_SECTION_BACKREST && $strKey eq INI_KEY_CHECKSUM))
|
|
{
|
|
$strContent .= "${strKey}=" . $oJSON->encode($strValue) . "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
$bFirst = false;
|
|
}
|
|
|
|
# If there is a checksum write it at the end of the file. Having the checksum at the end of the file allows some major
|
|
# performance optimizations which we won't implement in Perl, but will make the C code much more efficient.
|
|
if (!$bRelaxed && defined($oContent->{&INI_SECTION_BACKREST}) && defined($oContent->{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM}))
|
|
{
|
|
$strContent .=
|
|
"\n[" . INI_SECTION_BACKREST . "]\n" .
|
|
INI_KEY_CHECKSUM . '=' . $oJSON->encode($oContent->{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM}) . "\n";
|
|
}
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'strContent', value => $strContent, trace => true}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# hash() - generate hash for the manifest.
|
|
####################################################################################################################################
|
|
sub hash
|
|
{
|
|
my $self = shift;
|
|
|
|
# Remove the old checksum
|
|
delete($self->{oContent}{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM});
|
|
|
|
# Set the new checksum
|
|
$self->{oContent}{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM} =
|
|
sha1_hex(JSON::PP->new()->canonical()->allow_nonref()->encode($self->{oContent}));
|
|
|
|
return $self->{oContent}{&INI_SECTION_BACKREST}{&INI_KEY_CHECKSUM};
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# get() - get a value.
|
|
####################################################################################################################################
|
|
sub get
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
my $bRequired = shift;
|
|
my $oDefault = shift;
|
|
|
|
# Parameter constraints
|
|
if (!defined($strSection))
|
|
{
|
|
confess &log(ASSERT, 'strSection is required');
|
|
}
|
|
|
|
if (defined($strSubKey) && !defined($strKey))
|
|
{
|
|
confess &log(ASSERT, "strKey is required when strSubKey '${strSubKey}' is requested");
|
|
}
|
|
|
|
# Get the result
|
|
my $oResult = $self->{oContent}->{$strSection};
|
|
|
|
if (defined($strKey) && defined($oResult))
|
|
{
|
|
$oResult = $oResult->{$strKey};
|
|
|
|
if (defined($strSubKey) && defined($oResult))
|
|
{
|
|
$oResult = $oResult->{$strSubKey};
|
|
}
|
|
}
|
|
|
|
# When result is not defined
|
|
if (!defined($oResult))
|
|
{
|
|
# Error if a result is required
|
|
if (!defined($bRequired) || $bRequired)
|
|
{
|
|
confess &log(ASSERT, "strSection '$strSection'" . (defined($strKey) ? ", strKey '$strKey'" : '') .
|
|
(defined($strSubKey) ? ", strSubKey '$strSubKey'" : '') . ' is required but not defined');
|
|
}
|
|
|
|
# Return default if specified
|
|
if (defined($oDefault))
|
|
{
|
|
return $oDefault;
|
|
}
|
|
}
|
|
|
|
return $oResult
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# boolGet() - get a boolean value.
|
|
####################################################################################################################################
|
|
sub boolGet
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
my $bRequired = shift;
|
|
my $bDefault = shift;
|
|
|
|
return $self->get(
|
|
$strSection, $strKey, $strSubKey, $bRequired,
|
|
defined($bDefault) ? ($bDefault ? INI_TRUE : INI_FALSE) : undef) ? true : false;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# numericGet() - get a numeric value.
|
|
####################################################################################################################################
|
|
sub numericGet
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
my $bRequired = shift;
|
|
my $nDefault = shift;
|
|
|
|
return $self->get($strSection, $strKey, $strSubKey, $bRequired, defined($nDefault) ? $nDefault + 0 : undef) + 0;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# set - set a value.
|
|
####################################################################################################################################
|
|
sub set
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
my $oValue = shift;
|
|
|
|
# Parameter constraints
|
|
if (!(defined($strSection) && defined($strKey)))
|
|
{
|
|
confess &log(ASSERT, 'strSection and strKey are required');
|
|
}
|
|
|
|
my $oCurrentValue;
|
|
|
|
if (defined($strSubKey))
|
|
{
|
|
$oCurrentValue = \$self->{oContent}{$strSection}{$strKey}{$strSubKey};
|
|
}
|
|
else
|
|
{
|
|
$oCurrentValue = \$self->{oContent}{$strSection}{$strKey};
|
|
}
|
|
|
|
if (!defined($$oCurrentValue) ||
|
|
defined($oCurrentValue) != defined($oValue) ||
|
|
${dclone($oCurrentValue)} ne ${dclone(\$oValue)})
|
|
{
|
|
$$oCurrentValue = $oValue;
|
|
|
|
if (!$self->{bModified})
|
|
{
|
|
$self->{bModified} = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# boolSet - set a boolean value.
|
|
####################################################################################################################################
|
|
sub boolSet
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
my $bValue = shift;
|
|
|
|
$self->set($strSection, $strKey, $strSubKey, $bValue ? INI_TRUE : INI_FALSE);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# numericSet - set a numeric value.
|
|
####################################################################################################################################
|
|
sub numericSet
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
my $nValue = shift;
|
|
|
|
$self->set($strSection, $strKey, $strSubKey, defined($nValue) ? $nValue + 0 : undef);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# remove - remove a value.
|
|
####################################################################################################################################
|
|
sub remove
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strKey = shift;
|
|
my $strSubKey = shift;
|
|
|
|
# Test if the value exists
|
|
if ($self->test($strSection, $strKey, $strSubKey))
|
|
{
|
|
# Remove a subkey
|
|
if (defined($strSubKey))
|
|
{
|
|
delete($self->{oContent}{$strSection}{$strKey}{$strSubKey});
|
|
}
|
|
|
|
# Remove a key
|
|
if (defined($strKey))
|
|
{
|
|
if (!defined($strSubKey))
|
|
{
|
|
delete($self->{oContent}{$strSection}{$strKey});
|
|
}
|
|
|
|
# Remove the section if it is now empty
|
|
if (keys(%{$self->{oContent}{$strSection}}) == 0)
|
|
{
|
|
delete($self->{oContent}{$strSection});
|
|
}
|
|
}
|
|
|
|
# Remove a section
|
|
if (!defined($strKey))
|
|
{
|
|
delete($self->{oContent}{$strSection});
|
|
}
|
|
|
|
# Record changes
|
|
if (!$self->{bModified})
|
|
{
|
|
$self->{bModified} = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# keys - get the list of keys in a section.
|
|
####################################################################################################################################
|
|
sub keys
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strSortOrder = shift;
|
|
|
|
if ($self->test($strSection))
|
|
{
|
|
if (!defined($strSortOrder) || $strSortOrder eq INI_SORT_FORWARD)
|
|
{
|
|
return (sort(keys(%{$self->get($strSection)})));
|
|
}
|
|
elsif ($strSortOrder eq INI_SORT_REVERSE)
|
|
{
|
|
return (sort {$b cmp $a} (keys(%{$self->get($strSection)})));
|
|
}
|
|
elsif ($strSortOrder eq INI_SORT_NONE)
|
|
{
|
|
return (keys(%{$self->get($strSection)}));
|
|
}
|
|
else
|
|
{
|
|
confess &log(ASSERT, "invalid strSortOrder '${strSortOrder}'");
|
|
}
|
|
}
|
|
|
|
my @stryEmptyArray;
|
|
return @stryEmptyArray;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# test - test a value.
|
|
#
|
|
# Test a value to see if it equals the supplied test value. If no test value is given, tests that the section, key, or subkey
|
|
# is defined.
|
|
####################################################################################################################################
|
|
sub test
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strValue = shift;
|
|
my $strSubValue = shift;
|
|
my $strTest = shift;
|
|
|
|
# Get the value
|
|
my $strResult = $self->get($strSection, $strValue, $strSubValue, false);
|
|
|
|
# Is there a result
|
|
if (defined($strResult))
|
|
{
|
|
# Is there a value to test against?
|
|
if (defined($strTest))
|
|
{
|
|
# Make sure these are explicit strings or Devel::Cover thinks they are equal if one side is a boolean
|
|
return ($strResult . '') eq ($strTest . '') ? true : false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# boolTest - test a boolean value, see test().
|
|
####################################################################################################################################
|
|
sub boolTest
|
|
{
|
|
my $self = shift;
|
|
my $strSection = shift;
|
|
my $strValue = shift;
|
|
my $strSubValue = shift;
|
|
my $bTest = shift;
|
|
|
|
return $self->test($strSection, $strValue, $strSubValue, defined($bTest) ? ($bTest ? INI_TRUE : INI_FALSE) : undef);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# cipherPassSub - gets the passphrase (if it exists) used to read/write subsequent files
|
|
####################################################################################################################################
|
|
sub cipherPassSub
|
|
{
|
|
my $self = shift;
|
|
|
|
return $self->get(INI_SECTION_CIPHER, INI_KEY_CIPHER_PASS, undef, false);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# Properties.
|
|
####################################################################################################################################
|
|
sub modified {shift->{bModified}} # Has the data been modified since last load/save?
|
|
sub exists {shift->{bExists}} # Is the data persisted to file?
|
|
sub cipherPass {shift->{strCipherPass}} # Return passphrase (will be undef if repo not encrypted)
|
|
|
|
1;
|