####################################################################################################################################
# Posix File Write
####################################################################################################################################
package pgBackRestTest::Common::StoragePosixWrite;
use parent 'pgBackRestTest::Common::Io::Handle';

use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

use Fcntl qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
use File::Basename qw(dirname);

use pgBackRestDoc::Common::Exception;
use pgBackRestDoc::Common::Log;

use pgBackRestTest::Common::Io::Handle;
use pgBackRestTest::Common::StorageBase;

####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
    my $class = shift;

    # Assign function parameters, defaults, and log debug info
    my
    (
        $strOperation,
        $oDriver,
        $strName,
        $strMode,
        $strUser,
        $strGroup,
        $lTimestamp,
        $bPathCreate,
        $bAtomic,
        $bSync,
    ) =
        logDebugParam
        (
            __PACKAGE__ . '->new', \@_,
            {name => 'oDriver', trace => true},
            {name => 'strName', trace => true},
            {name => 'strMode', optional => true, trace => true},
            {name => 'strUser', optional => true, trace => true},
            {name => 'strGroup', optional => true, trace => true},
            {name => 'lTimestamp', optional => true, trace => true},
            {name => 'bPathCreate', optional => true, default => false, trace => true},
            {name => 'bAtomic', optional => true, default => false, trace => true},
            {name => 'bSync', optional => true, default => true, trace => true},
        );

    # Create the class hash
    my $self = $class->SUPER::new("'${strName}'");
    bless $self, $class;

    # Set variables
    $self->{oDriver} = $oDriver;
    $self->{strName} = $strName;
    $self->{strMode} = $strMode;
    $self->{strUser} = $strUser;
    $self->{strGroup} = $strGroup;
    $self->{lTimestamp} = $lTimestamp;
    $self->{bPathCreate} = $bPathCreate;
    $self->{bAtomic} = $bAtomic;
    $self->{bSync} = $bSync;

    # If atomic create temp filename
    if ($self->{bAtomic})
    {
        # Create temp file name
        $self->{strNameTmp} = "$self->{strName}." . $self->{oDriver}->tempExtension();
    }

    # Open file on first write to avoid creating extraneous files on error
    $self->{bOpened} = false;

    # Return from function and log return values if any
    return logDebugReturn
    (
        $strOperation,
        {name => 'self', value => $self, trace => true}
    );
}

####################################################################################################################################
# open - open the file
####################################################################################################################################
sub open
{
    my $self = shift;

    # Get the file name
    my $strFile = $self->{bAtomic} ? $self->{strNameTmp} : $self->{strName};

    # Open the file
    if (!sysopen(
        $self->{fhFile}, $strFile, O_WRONLY | O_CREAT | O_TRUNC, oct(defined($self->{strMode}) ? $self->{strMode} : '0666')))
    {
        # If the path does not exist create it if requested
        if ($OS_ERROR{ENOENT} && $self->{bPathCreate})
        {
            $self->{oDriver}->pathCreate(dirname($strFile), {bIgnoreExists => true, bCreateParent => true});
            $self->{bPathCreate} = false;
            return $self->open();
        }

        logErrorResult($OS_ERROR{ENOENT} ? ERROR_PATH_MISSING : ERROR_FILE_OPEN, "unable to open '${strFile}'", $OS_ERROR);
    }

    # Set file mode to binary
    binmode($self->{fhFile});

    # Set the owner
    $self->{oDriver}->owner($strFile, {strUser => $self->{strUser}, strGroup => $self->{strGroup}});

    # Set handle
    $self->handleWriteSet($self->{fhFile});

    # Mark file as opened
    $self->{bOpened} = true;

    return true;
}

####################################################################################################################################
# write - write data to a file
####################################################################################################################################
sub write
{
    my $self = shift;
    my $rtBuffer = shift;

    # Open file if it is not open already
    $self->open() if !$self->opened();

    return $self->SUPER::write($rtBuffer);
}

####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
    my $self = shift;

    if (defined($self->handle()))
    {
        # Sync the file
        if ($self->{bSync})
        {
            $self->handle()->sync();
        }

        # Close the file
        close($self->handle());
        undef($self->{fhFile});

        # Get current filename
        my $strCurrentName = $self->{bAtomic} ? $self->{strNameTmp} : $self->{strName};

        # Set the modification time
        if (defined($self->{lTimestamp}))
        {
            utime(time(), $self->{lTimestamp}, $strCurrentName)
                or logErrorResult(ERROR_FILE_WRITE, "unable to set time for '${strCurrentName}'", $OS_ERROR);
        }

        # Move the file from temp to final if atomic
        if ($self->{bAtomic})
        {
            $self->{oDriver}->move($strCurrentName, $self->{strName});
        }

        # Set result
        $self->resultSet(COMMON_IO_HANDLE, $self->{lSize});

        # Close parent
        $self->SUPER::close();
    }

    return true;
}

####################################################################################################################################
# Close the handle if it is open (in case close() was never called)
####################################################################################################################################
sub DESTROY
{
    my $self = shift;

    if (defined($self->handle()))
    {
        CORE::close($self->handle());
        undef($self->{fhFile});
    }
}

####################################################################################################################################
# Getters
####################################################################################################################################
sub handle {shift->{fhFile}}
sub opened {shift->{bOpened}}
sub name {shift->{strName}}

1;
