#!/usr/bin/perl -w

#
# File:
#   ag_smt_staging
#
# Authors:
#   Lukas Ocilka <locilka@suse.cz>
#
# Description:
#   SCR agent for repositories staging management
#
# $Id$
#


use lib "/usr/lib/YaST2/agents_non_y2";
use ycp;
use strict;

use SMT::Utils;
use SMT::CLI;
use SMT::Filter;
use SMT::Client;
use SMT::Repositories;
use SMT::Parser::RpmMdPatches;
use SMT::Mirror::RpmMd;
use SMT::Mirror::Yum;
use SMT::Mirror::Utils;

# For debugging only
# use Data::Dumper;

# Suppress printing logs to STDOUT, to nemaji kralicci radi
# (SCR uses stdin/stdout for communication with agents)
SMT::Utils::setLogBehavior ({'doprint' => 0});

if(! SMT::Utils::dropPrivileges()) {
    y2error ('Unable to drop privileges. Aborting...');
    ycp::Return (undef);
    exit 1;
}

# Initialize SMT
my ($cfg, $dbh, $nuri) = SMT::CLI::init();
my $base_path = $cfg->val('LOCAL', 'MirrorTo') || '/srv/www/htdocs';

# Used for re-signing repositories (during staging)
my $keyid	= $cfg->val('LOCAL', 'signingKeyID');
my $passphrase	= $cfg->val('LOCAL', 'signingKeyPassphrase');

my $repositories = SMT::Repositories::new($dbh);
my $clients = SMT::Client->new({'dbh' => $dbh});

use constant {
    YCP_TRUE	=> 'true',
    YCP_FALSE	=> 'false',

    # All Mirrored repositories with Staging feature enabled
    MIRRORED_REPO_FILTER => {
        SMT::Repositories::MIRRORING	=> SMT::Repositories::MIRRORING_TRUE,
        SMT::Repositories::STAGING	=> SMT::Repositories::STAGING_TRUE,
    },

    # All Mirrored repositories with Staging feature enabled
    ALL_REPO_FILTER => {
        SMT::Repositories::MIRRORABLE	=> SMT::Repositories::MIRRORABLE_TRUE,
    },
};

my $log = SMT::Utils::openLog ('/var/log/smt/smt-staging.log');
my $vblevel = LOG_DEBUG|LOG_DEBUG2|LOG_WARN|LOG_ERROR|LOG_INFO1|LOG_INFO2;

# Reports that the agent has been called using an unsupported path
sub UnsupportedPath ($$) {
    my ($command_ref, $path_ref) = (shift, shift);

    y2error ('Unsupported path: '.$$path_ref.' for command '.$$command_ref);
    ycp::Return (undef);
}

# Filters and Patches Handles
my $fph = {};

# Initializes required handles (in not yet initialized)
# loads filters
sub LoadRepositoryData ($$) {
    my $repositoryid = shift || "";
    my $group = shift || "default";

    # No repository ID defined, unable to load data
    if ($repositoryid eq "") {
        y2error ("Repository ID must be defined");
        return undef;
    }

    # Already loaded
    if (defined $fph->{"$repositoryid-$group"}) {
        y2debug ('Repository '."$repositoryid-$group".' already loaded');
        return 1;
    }

    my $ret = 1;

    # New empty Handler
    $fph->{"$repositoryid-$group"} = {};

    # New Filter handler
    $fph->{"$repositoryid-$group"}->{'fh'} = SMT::Filter->new();
    if (! $fph->{"$repositoryid-$group"}->{'fh'}->load($dbh, $repositoryid, $group)) {
        y2error ("Cannot load filters for repository: ".$repositoryid." Group:".$group);
        $ret = 0;
    }

    # Repository path relative to the base-path
    my $repo_path_suffix = $repositories->getRepositoryPath($repositoryid) || do {
        y2error ('Unable to load repository data for: '.$repositoryid);
        return;
    };

    # repo/$REPOSITORY
    # repo/testing/$REPOSITORY
    # repo/full/$REPOSITORY
    $fph->{"$repositoryid-$group"}->{'paths'} = {
        # The base local path to all repos
        'base'       => $base_path.'/repo/',
        # Particular subrepositories
        # * 'full' contains the mirrored data
        'full'       => $repositories->getFullRepoPath($repositoryid, $base_path, $group),
        # * 'testing' is a snapshot intended for testing
        'testing'    => $repositories->getTestingRepoPath($repositoryid, $base_path, $group),
        # * 'production' is a snapshot intended for production
        'production' => $repositories->getProductionRepoPath($repositoryid, $base_path, $group),
    };

    y2milestone ("Parsing local repository: ".$fph->{"$repositoryid-$group"}->{'paths'}->{'full'});

    $fph->{"$repositoryid-$group"}->{'ph'} = SMT::Parser::RpmMdPatches->new();
    $fph->{"$repositoryid-$group"}->{'ph'}->resource($fph->{"$repositoryid-$group"}->{'paths'}->{'full'});
    $fph->{"$repositoryid-$group"}->{'ph'}->parse("repodata/updateinfo.xml.gz", "repodata/patches.xml");

    # FIXME: load all this on-the-fly to speed it up

    my $testing = SMT::Parser::RpmMdPatches->new();
    $testing->resource($fph->{"$repositoryid-$group"}->{'paths'}->{'testing'});
    $testing->parse("repodata/updateinfo.xml.gz", "repodata/patches.xml");
    # $testing->{PATCHES}->{$patch_name-$patch_version}
    $testing = $testing->{PATCHES};

    my $production = SMT::Parser::RpmMdPatches->new();
    $production->resource($fph->{"$repositoryid-$group"}->{'paths'}->{'production'});
    $production->parse("repodata/updateinfo.xml.gz", "repodata/patches.xml");
    # $production->{PATCHES}->{$patch_name-$patch_version}
    $production = $production->{PATCHES};

    my @all_patches = keys (%{$fph->{"$repositoryid-$group"}->{'ph'}->{PATCHES}});
    my $patchname;

    foreach $patchname (@all_patches) {
        $fph->{"$repositoryid-$group"}->{'ph'}->{PATCHES}->{$patchname}->{'testing'} =
            (defined $testing->{$patchname} ? YCP_TRUE:YCP_FALSE);
        $fph->{"$repositoryid-$group"}->{'ph'}->{PATCHES}->{$patchname}->{'production'} =
            (defined $production->{$patchname} ? YCP_TRUE:YCP_FALSE);
    }

    return $ret;
}

# Goes through all the SMT::Filter handles and saves the data
# repository by repository
sub WritePatchFilters () {
    my $repositoryid = '';
    my $group = '';
    my $handlerid = '';

    my $ret = 1;

    # Runs through all already loaded repositories
    foreach $handlerid (keys %{$fph}) {
        ($repositoryid, $group) = split(/-/, $handlerid, 2);
        y2milestone ("Calling save() on Catalog:".$repositoryid." Group: $group");

        if (! defined $fph->{$handlerid}->{'fh'}) {
            y2warning ("FilterHandle not defined for repo: ".$repositoryid. " Group: $group");
        } elsif (! $fph->{$handlerid}->{'fh'}->save($dbh, $repositoryid, $group)) {
            y2error ("Error saving catalog ".$repositoryid." Group: $group");
            $ret = 0;
        }
    }

    return ($ret ? YCP_TRUE:YCP_FALSE);
}

# Returns patches with their status
sub GetAllPatches ($) {
    my $arg = shift || {};
    my $repositoryid = $arg->{repositoryid} || "";
    my $group = $arg->{group} || "default";

    if ($repositoryid eq "") {
        y2error ("Catalog ID must be defined");
        return undef;
    }

    y2milestone ("Handling repositoryid: ".$repositoryid. " Group: $group");

    LoadRepositoryData($repositoryid, $group);

    my @ret;
    my $patchname = "";
    my $tmppatch = {};

    foreach my $patchid (keys %{$fph->{"$repositoryid-$group"}->{'ph'}->{'PATCHES'}}) {
        $tmppatch = $fph->{"$repositoryid-$group"}->{'ph'}->{'PATCHES'}->{$patchid};
        $tmppatch->{'patchid'} = $patchid;
        $tmppatch->{'filtered'} = ($fph->{"$repositoryid-$group"}->{'fh'}->matches($tmppatch) ? YCP_TRUE:YCP_FALSE);

        # Transforming packages description from epo, ver, rel to [epo:]ver-rel
        # Perl->YCP conversion then never treats with version as integer
        # BNC #670654
        my $pkgs = [];
        foreach my $pkg (@{$tmppatch->{'pkgs'}}) {
            my $new_pkg = {
                'name' => (defined $pkg->{'name'} ? $pkg->{'name'}:''),
                'arch' => (defined $pkg->{'arch'} ? $pkg->{'arch'}:''),
                # Leading zero (default epoch) is ignored
                'version' => (defined $pkg->{'epo'} && $pkg->{'epo'} ne '0' ? $pkg->{'epo'}.':':'').
                (defined $pkg->{'ver'} ? $pkg->{'ver'}:'0').'-'.
                (defined $pkg->{'rel'} ? $pkg->{'rel'}:'0'),
            };
            push @{$pkgs}, $new_pkg;
        }
        $tmppatch->{'pkgs'} = $pkgs;

        push @ret, $tmppatch;
    }

    return \@ret;
}

# Changes the patch status by adding or removing TYPE_NAME_VERSION filter
# Patches can be matching another filters though
sub ChangePatchStatus ($) {
    my $params = shift;

    my $repositoryid = $params->{'repositoryid'} || '';
    my $group        = $params->{'group'}        || 'default';
    my $patchid      = $params->{'patchid'}      || '';
    my $new_status   = $params->{'status'}       || '';

    if ($repositoryid eq '') {
        y2error ("Parameter 'repositoryid' not defined");
        return YCP_FALSE;
    }
    if ($patchid eq '') {
        y2error ("Parameter 'patchid' not defined");
        return YCP_FALSE;
    }

    if (! GetStagingAllowedInternal({'repositoryid' => $repositoryid})) {
        y2error ('Repository '.$repositoryid.' does not allow filtering');
    }

    LoadRepositoryData($repositoryid, $group);

    # true -> wanted in testig repository
    if ($new_status) {
        $fph->{"$repositoryid-$group"}->{'fh'}->remove(SMT::Filter::TYPE_NAME_VERSION, $patchid);
        # false -> not wanted
    } else {
        $fph->{"$repositoryid-$group"}->{'fh'}->add(SMT::Filter::TYPE_NAME_VERSION, $patchid);
    }

    return YCP_TRUE;
}

# Changes status of a filter by category
sub ChangeCategoryFilterStatus ($) {
    my $params = shift;

    my $repositoryid = $params->{'repositoryid'}  || '';
    my $group        = $params->{'group'}         || 'default';
    my $type         = $params->{'type'}          || '';
    my $new_status   = $params->{'status'}        || '';

    if ($repositoryid eq '') {
        y2error ("Parameter 'repositoryid' not defined");
        return YCP_FALSE;
    }
    if ($type eq '') {
        y2error ("Parameter 'type' not defined");
        return YCP_FALSE;
    }

    LoadRepositoryData($repositoryid, $group);

    # true -> filter is required
    if ($new_status) {
        $fph->{"$repositoryid-$group"}->{'fh'}->add(SMT::Filter::TYPE_SECURITY_LEVEL, $type);
        # false -> remove filter
    } else {
        $fph->{"$repositoryid-$group"}->{'fh'}->remove(SMT::Filter::TYPE_SECURITY_LEVEL, $type);
    }

    return YCP_TRUE;
}

# Returns whether patch is enabled in snapshot
# == not disabled by filter
sub GetPatchStatus ($) {
    my $params = shift;

    my $repositoryid = $params->{'repositoryid'} || '';
    my $group        = $params->{'group'}        || 'default';
    my $patchid      = $params->{'patchid'}      || '';

    if ($repositoryid eq '') {
        y2error ("Parameter 'repositoryid' not defined");
        return undef;
    }
    if ($patchid eq '') {
        y2error ("Parameter 'patchid' not defined");
        return undef;
    }

    LoadRepositoryData($repositoryid, $group);

    my $this_patch = $fph->{"$repositoryid-$group"}->{'ph'}->{'PATCHES'}->{$patchid};
    # The current patch status -> ! matching (filters)
    return ($fph->{"$repositoryid-$group"}->{'fh'}->matches($this_patch) ? YCP_FALSE:YCP_TRUE);
}

# Returns whether a given filter exists
#
# $param->{'repositoryid'} - hash to identyfy a catalog
# $param->{'type'} - one of the well known types
# $param->{'filter'} - filter string
sub FilterExists ($) {
    my $params = shift;

    my $repositoryid = $params->{'repositoryid'} || '';
    my $group        = $params->{'group'}        | 'default';
    my $type         = $params->{'type'}         || '';
    my $filter       = $params->{'filter'}       || '';

    if ($repositoryid eq '') {
        y2error ("Parameter 'repositoryid' not defined");
        return undef;
    }
    if ($type eq '') {
        y2error ("Parameter 'type' not defined");
        return undef;
    }
    if ($filter eq '') {
        y2error ("Parameter 'filter' not defined");
        return undef;
    }

    LoadRepositoryData($repositoryid, $group);
    return $fph->{"$repositoryid-$group"}->{'fh'}->contains($type, $filter);
}

# Returns whether filter by patch type exists (is active)
sub SecurityLevelFilterExists ($) {
    my $arg = shift;
    my $repositoryid = $arg->{'repositoryid'} || do {
        y2error ("Parameter 'repositoryid' not defined");
        return undef;
    };
    my $group = $arg->{'group'} || 'default';

    LoadRepositoryData($repositoryid, $group);

    my $func_args = {
        'type' => SMT::Filter::TYPE_SECURITY_LEVEL,
        # filter string is the security level of a patch
        'filter' => (defined $arg->{'type'} ? $arg->{'type'}:''),
        'repositoryid' => $repositoryid,
        'group' => $group
    };

    return (FilterExists ($func_args) ? YCP_TRUE:YCP_FALSE);
}

sub CreateSnapshot ($) {
    my $arg = shift || {};

    # Identifies the repository
    my $repositoryid = $arg->{'repositoryid'} || do {
        y2error ("Parameter 'repositoryid' not defined");
        return YCP_FALSE;
    };

    # Defines a type of generated subrepository
    my $type = $arg->{'type'} || do {
        y2error ("Parameter 'type' not defined");
        return YCP_FALSE;
    };
    my $group = $arg->{'group'} || 'default';

    # Fills up also $fph->{$repositoryid}->{'paths'}
    LoadRepositoryData($repositoryid, $group);

    # 'testing' or 'production' or ...
    if (not defined $fph->{"$repositoryid-$group"}->{'paths'}->{$type}) {
        y2error ("Unknown 'type': ".$type);
        return YCP_FALSE;
    }

    my $mirrorsrc = $cfg->val('LOCAL', 'MirrorSRC');
    if (defined $mirrorsrc && lc($mirrorsrc) eq "false")
    { $mirrorsrc = 0; }
    else
    { $mirrorsrc = 1; }

    my %mirror_args = (
        log => $log,
        dbh => $dbh,
        vblevel => $vblevel,
        mirrorsrc => $mirrorsrc
    );

    my $staging_allowed = GetStagingAllowedInternal ({'repositoryid' => $repositoryid});

    # Use filters only if staging (filtering) is allowed and...
    # Additionally, filters are used for 'full->testing' only
    if ($staging_allowed && $type eq 'testing') {
        y2milestone ('Filtering is allowed, using all defined filters');
        $mirror_args{'filter'} = $fph->{"$repositoryid-$group"}->{'fh'};
    } else {
        y2milestone ('Not using filters');
    }

    # The default source
    my $staging_source = '';

    # If there are no filters allowed, we create the production snapshot from the testing one
    # because the testing repository is actually the one we want to have in production snapshot
    if ($type eq 'testing') {
        $staging_source = $fph->{"$repositoryid-$group"}->{'paths'}->{'full'};
    } elsif ($type eq 'production') {
        my $check_source = $fph->{"$repositoryid-$group"}->{'paths'}->{'testing'};

        # Testing snapshot exists and is not empty
        # And contains a flag: successful mirroring
        if (-e $check_source && -d $check_source && -s $check_source && SMT::Mirror::Utils::getStatus ($check_source)) {
            y2milestone ('Using \'testing\' repository as source for \'production\'');
            $staging_source = $check_source;
        } else {
            y2error ('Cannot use \'testing\' repository as a source for \'production\'');
            # FIXME: return the error to UI
            return YCP_FALSE;
        }
    } else {
        y2error ('Unknown type: \''.$type.'\'');
    }

    my $mirror = SMT::Mirror::RpmMd->new(%mirror_args);

    if(-d "$staging_source/headers")
    {
        $mirror = SMT::Mirror::Yum->new(%mirror_args);
    }

    # The source of data
    y2milestone ('Source: '.$staging_source);
    $mirror->uri('file://'.$staging_source);
    my $local_repo_path = "";
    my $basepath = $fph->{"$repositoryid-$group"}->{'paths'}->{'base'};
    if ($fph->{"$repositoryid-$group"}->{'paths'}->{$type} =~ /^($basepath)(.+)/)
    {
        $local_repo_path = $2;
    }
    else
    {
        y2error ('Invalid repo path');
    }
    # Where the resulting subrepository is created
    y2milestone (
        'Base: '.$fph->{"$repositoryid-$group"}->{'paths'}->{'base'}.' '.
        'Repo: '.$local_repo_path.' '.
        'Type: ('.$type.')'
    );
    $mirror->localBasePath($fph->{"$repositoryid-$group"}->{'paths'}->{'base'});
    $mirror->localRepoPath($local_repo_path);

    # Forcing mirroring
    my %args = ('force' => 1);
    my $passphrase_cached = 0;

    # 'signingKeyID' should be used, passphrase is set
    if (defined $arg->{'key'} && defined $arg->{'passphrase'}) {
        y2milestone ("Using signing key ID: ".$arg->{'key'});

        # 'signingKeyPassphrase' (from smt.conf is not defined) but it came as a parameter
        if (! defined $passphrase) {
            y2milestone ("Using passphrase defined as a parameter");
            $passphrase = $arg->{'passphrase'};
            $passphrase_cached = 1;
        }

        $args{keyid}    = $arg->{'key'};
        $args{keypass}  = $passphrase;
    }

    # And GO!
    y2milestone ("Running mirror()");
    my $ret = $mirror->mirror (%args);
    y2milestone ("mirror() returned: ".(defined $ret ? $ret:'nil'));

    if (defined $ret && $ret == 0) {
        SMT::Mirror::Utils::copyStatus($staging_source,
                                       $fph->{"$repositoryid-$group"}->{'paths'}->{$type}
        );
        SMT::Utils::printLog($log, $vblevel, LOG_INFO1,
                             sprintf __("Repository successfully generated at %s."),
                             $fph->{"$repositoryid-$group"}->{'paths'}->{$type}
        );
    } else {
        SMT::Utils::printLog($log, $vblevel, LOG_ERROR,
                             sprintf __("Unable to generated repository %s."),
                             $fph->{"$repositoryid-$group"}->{'paths'}->{$type}
        );
        # Reset the changed passphrase if generating failed
        $passphrase = undef if ($passphrase_cached);
    }

    # FIXME: Invalidate/re-read only the piece of cache which has just been rewritten
    # (testing / production)
    $fph->{"$repositoryid-$group"} = undef;
    LoadRepositoryData($repositoryid, $group);

    # Contains number of errors
    return (defined $ret && $ret == 0 ? YCP_TRUE:YCP_FALSE);
}

# Cache for 'filteringAllowed()' results
my $st_allowed_cache = {};

sub GetStagingAllowedInternal ($) {
    my $arg = shift || {};

    my $repositoryid = $arg->{'repositoryid'} || do {
        y2error ('Parameter repositoryid must be defined');
        return undef;
    };

    # Purge the old result to recheck
    if (defined $arg->{'force_check'} && defined $st_allowed_cache->{$repositoryid}) {
        y2milestone ('Purging cached info for repository '.$repositoryid.' on request');
        $st_allowed_cache->{$repositoryid} = undef;
    }

    my $allowed = 0;
    my $errors = '';

    if (defined $st_allowed_cache->{$repositoryid}) {
        $allowed = $st_allowed_cache->{$repositoryid};
    } else {
        # Clear all the previous errors
        $repositories->getAndClearErrorMessage();
        $allowed = $repositories->filteringAllowed($repositoryid, $base_path);
        $errors = $repositories->getAndClearErrorMessage();
        # Cache the result
        $st_allowed_cache->{$repositoryid} = $allowed;
    }

    if ($errors) {
        foreach my $eline (split (/\n/, $errors)) {
            y2error ("stagingAllowed: ".$eline);
        }
    }

    return $allowed;
}

sub GetStagingAllowed ($) {
    my $arg = shift || {};

    return (GetStagingAllowedInternal($arg) ? YCP_TRUE:YCP_FALSE);
}

sub AdjustRepository ($) {
    my $arg = shift || {};

    my $repositoryid = $arg->{'repositoryid'} || do {
        y2error ('Parameter repositoryid must be defined');
        return undef;
    };

    if (! defined $arg->{'mirroring'} && ! defined $arg->{'staging'}) {
        y2error ('Parameters staging or mirroring must be defined');
        return undef;
    }
    my $group = $arg->{group} || 'default';

    LoadRepositoryData ($repositoryid, $group);

    my $result1 = 1;
    my $result2 = 1;

    if (defined $arg->{'mirroring'}) {
        # returns number of rows changed
        $result1 = SMT::CLI::setCatalogDoMirror(
            enabled => $arg->{'mirroring'}, id => $arg->{'repositoryid'});
    }
    if (defined $arg->{'staging'}) {
        # returns number of rows changed
        $result2 = SMT::CLI::setCatalogStaging(
            enabled => $arg->{'staging'}, id => $arg->{'repositoryid'});
    }

    return ($result1 && $result2 ? YCP_TRUE : YCP_FALSE);
}

sub IsSubrepositoryUpToDate ($) {
    my $arg = shift || {};

    my $repositoryid = $arg->{'repositoryid'} || do {
        y2error ('Parameter repositoryid must be defined');
        return undef;
    };
    my $group = $arg->{'group'} || 'default';

    LoadRepositoryData ($repositoryid, $group);

    my $repo_args = {
        'repositoryid' => $arg->{'repositoryid'},
        'type' => $arg->{'type'},
        'basepath' => $base_path,
        'staginggroup' => $group,
    };

    return ($repositories->isSnapshotUpToDate($repo_args) ? YCP_TRUE:YCP_FALSE);
}

sub GetDateFromTimestamp ($) {
    my $timestamp = shift || do { return undef; };

    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime ($timestamp);

    # BNC #537087: Months are zero-based
    return ($year + 1900).'-'.sprintf('%02d', $mon + 1).'-'.sprintf('%02d', $mday).' '.
    sprintf('%02d', $hour).':'.sprintf('%02d', $min).':'.sprintf('%02d', $sec);
}

sub GetRepositoryDetails ($) {
    my $arg = shift || {};

    my $repositoryid = $arg->{'repositoryid'} || do {
        y2error ('Parameter repositoryid must be defined');
    return undef;
    };
    my $group = $arg->{'group'} || 'default';

    LoadRepositoryData ($repositoryid, $group);

    my $repo_args = {
        'repositoryid' => $arg->{'repositoryid'},
        'basepath'     => $base_path,
        'staginggroup' => $group,
    };

    my $ret = $repositories->getRepositoryDetails($repo_args);

    foreach my $key (keys %{$ret}) {
        if (defined $ret->{$key}) {
            $ret->{$key} = GetDateFromTimestamp ($ret->{$key});
        }
    }

    return $ret;
}

sub GetAllClientsStatus ($) {
    my $args = shift || {};

    my $info = $clients->getAllClientsInfo();
    my $info_copy = $info;

    my $id = '';
    foreach $id (keys %{$info_copy}) {
        ($info->{$id}->{STATUSLABEL}, $info->{$id}->{STATUSSTRING}) =
        SMT::Client::getPatchStatusLabel($info_copy->{$id});
    }

    return $info;
}

sub PurgeRepositoryCache ($) {
    my $arg = shift || {};
    my $repositoryid = $arg->{repositoryid};
    if(!$repositoryid)
    {
        y2error ('Repository ID must be defined');
        return YCP_FALSE;
    };
    my $group = $arg->{group} || 'default';

    $fph->{"$repositoryid-$group"} = undef if (defined $fph->{"$repositoryid-$group"});
    $st_allowed_cache->{$repositoryid} = undef if (defined $st_allowed_cache->{$repositoryid});
}

sub CheckThatDirectoryIsWritable ($) {
    my $directory = shift || do {
        y2error ('Directory not defined!');
        return 0;
    };

    if (! -e $directory) {
        y2error ($directory." does not exist");
        return 0;
    }

    if (! -w $directory) {
        return 0;
    }

    # Directory is writable
    return 1;
}

sub CheckRepository ($) {
    my $arg = shift || {};

    my $directory = $arg->{'directory'} || do {
        y2error ('Parameter directory must be defined');
        return undef;
    };

    return YCP_FALSE if (! CheckThatDirectoryIsWritable ($directory));

    opendir (DIR, $directory) || do {
        y2warning ("Cannot open directory: %1", $directory);
    };
    my @files = grep (!/^\.{1,2}/, readdir DIR);
    closedir (DIR);

    my $one_file;
    my $ret = 1;

    foreach $one_file (@files) {
        $one_file = $directory.'/'.$one_file;
        if (! CheckThatDirectoryIsWritable ($one_file)) {
            $ret = 0;
            last;
        }
    }

    return ($ret ? YCP_TRUE:YCP_FALSE);
}

# the main() loop
while (<STDIN>) {
    my ($command, $path, $arg) = ycp::ParseCommand ($_);
    # Just for debugging
    # y2internal ('Command:'.Dumper($command).' Path:'.Dumper($path).' Arg:'.Dumper($arg));

    # For Perl-only testing, otherwise already removed
    if ($path =~ /^\.smt/) {
        $path =~ s/^\.smt//;
    }

    if ($command eq 'Read') {
        if ($path eq '.staging.repositories') {
            ycp::Return ($repositories->getAllRepositories (MIRRORED_REPO_FILTER), 1);
        } elsif ($path eq '.staging.groups') {
            ycp::Return (SMT::Utils::getStagingGroupNames());
        } elsif ($path eq '.staging.patches') {
            ycp::Return (GetAllPatches ($arg));
        } elsif ($path eq '.staging.patch.status') {
            ycp::Return (GetPatchStatus ($arg));
        } elsif ($path eq '.staging.category_filter') {
            ycp::Return (SecurityLevelFilterExists ($arg));
        } elsif ($path eq '.staging.repository.uptodate') {
            ycp::Return (IsSubrepositoryUpToDate ($arg));
        } elsif ($path eq '.staging.repository.details') {
            ycp::Return (GetRepositoryDetails ($arg));
        } elsif ($path eq '.repositories.all') {
            ycp::Return ($repositories->getAllRepositories (ALL_REPO_FILTER), 1);
        } elsif ($path eq '.repository.staging_allowed') {
            ycp::Return (GetStagingAllowed ($arg));
        } elsif ($path eq '.clients.status') {
            ycp::Return (GetAllClientsStatus ($arg));
        } elsif ($path eq '.check_directory') {
            ycp::Return (CheckRepository ($arg));
        } else {
            UnsupportedPath (\$command, \$path);
        }
    } elsif ($command eq 'Write') {
        if ($path eq '.staging.patch.status') {
            ycp::Return (ChangePatchStatus ($arg));
        } elsif ($path eq '.staging.patches') {
            ycp::Return (WritePatchFilters());
        } elsif ($path eq '.staging.category_filter') {
            ycp::Return (ChangeCategoryFilterStatus ($arg));
        } elsif ($path eq '.staging.snapshot') {
            ycp::Return (CreateSnapshot ($arg));
        } elsif ($path eq '.repository.set') {
            ycp::Return (AdjustRepository ($arg));
        } else {
            UnsupportedPath (\$command, \$path);
        }
    } elsif ($command eq 'Execute') {
        if ($path eq '.repository.purge_cache') {
            ycp::Return (PurgeRepositoryCache ($arg));
        } else {
            UnsupportedPath (\$command, \$path);
            ycp::Return (undef);
        }
        # destructor
    } elsif ($command eq 'result') {
        y2milestone ('Bye bye...');
        last;
    } else {
        y2error ('Unknown SCR command: '.$command);
        ycp::Return (undef);
    }
}
# the main() loop

exit 0;
