Initial add of specialized Config modules. Some parts are not fully implemented.
authortobiasly <tobiasly>
Fri, 13 Dec 2002 05:46:08 +0000 (05:46 +0000)
committertobiasly <tobiasly>
Fri, 13 Dec 2002 05:46:08 +0000 (05:46 +0000)
lib/BackupPC/Config.pm [new file with mode: 0644]
lib/BackupPC/Config/Db.pm [new file with mode: 0644]
lib/BackupPC/Config/Db/MySQL.pm [new file with mode: 0644]
lib/BackupPC/Config/Text.pm [new file with mode: 0644]

diff --git a/lib/BackupPC/Config.pm b/lib/BackupPC/Config.pm
new file mode 100644 (file)
index 0000000..26ccead
--- /dev/null
@@ -0,0 +1,358 @@
+package BackupPC::Config;
+
+use warnings;
+use Data::Dumper;
+
+our %ConfigDef;
+
+# this currently isn't used (or completed)
+sub CheckConfigInfo
+{
+    my($self) = @_;
+    my $errstr = '';
+    
+    my($attr, $val, $def, $ref);
+    
+    foreach $attr (sort keys %{ $self->{Conf} }) {
+        print AA "Checking $attr...";
+        $val = $self->{Conf}->{$attr};
+        $ref = ref $val;
+        $def = $ConfigDef{$attr};
+        
+        if (!defined $def) {
+            $errstr .= "Unknown attribute $attr; ";
+        } elsif ($def->{struct} eq 'SCALAR' && $ref) {
+            $errstr .= "$attr expected to be SCALAR but is $ref; ";
+        } elsif ($def->{struct} =~ /^ARRAY(OFHASH)$/ && $ref && $ref ne 'ARRAY') {
+            $errstr .= "$attr expected to be ARRAY but is $ref; ";
+        } elsif ($def->{struct} =~ /^HASH(OFARRAY)$/ && $ref && $ref ne 'HASH') {
+            $errstr .= "$attr expected to be HASH but is $ref; ";
+        # still working out this logic..
+        #} elsif (defined $val && !$ref) {
+        #    # if we got a scalar but were expecting a reference, fix it
+        #    
+        #    if($def->{struct} eq 'ARRAY') {
+        #        $val = [ $val ];
+        #    } elsif ($def->{struct} eq 'HASH') {
+        #        $val = { $val };
+        #    } elsif ($def->{struct} eq 'ARRAYOFHASH') {
+        #        $val = [ { $val } ];
+        #    } elsif ($def->{struct} eq 'HASHOFARRAY') {
+        #        $val = { [ $val ] };
+        #    }
+            
+        }
+    }
+    
+    return $errstr;
+}
+
+sub TopDir
+{
+    my($self) = @_;
+    return $self->{TopDir};
+}
+
+sub BinDir
+{
+    my($self) = @_;
+    return $self->{BinDir};
+}
+
+sub Version
+{
+    my($self) = @_;
+    return $self->{Version};
+}
+
+sub Conf
+{
+    my($self) = @_;
+    return %{$self->{Conf}};
+}
+
+sub ConfigDef
+{
+    my($self) = @_;
+    return \%ConfigDef;
+}
+
+sub Lang
+{
+    my($self) = @_;
+    return $self->{Lang};
+}
+
+sub adminJob
+{
+    return " admin ";
+}
+
+sub trashJob
+{
+    return " trashClean ";
+}
+
+sub timeStamp
+{
+    my($self, $t, $noPad) = @_;
+    my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
+              = localtime($t || time);
+    $year += 1900;
+    $mon++;
+    return "$year/$mon/$mday " . sprintf("%02d:%02d:%02d", $hour, $min, $sec)
+            . ($noPad ? "" : " ");
+}
+
+sub ConnectData {
+    # fallback routine in case no database used
+    return 1;
+
+}
+
+###########################
+
+
+%ConfigDef = (
+    ServerHost                   => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    ServerPort                   => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    ServerMesgSecret             => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    MyPath                       => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    UmaskMode                    => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    WakeupSchedule               => {struct => 'ARRAY',
+                                     type   => 'INT', },
+
+    MaxBackups                   => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    MaxUserBackups               => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    MaxPendingCmds               => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    MaxOldLogFiles               => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    DfPath                       => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    DfMaxUsagePct                => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    TrashCleanSleepSec           => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    DHCPAddressRanges            => {struct => 'ARRAYOFHASH',
+                                     type   => {ipAddrBase => 'STRING',
+                                                first      => 'INT',
+                                                last       => 'INT',}, },
+
+    BackupPCUser                 => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiDir                       => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    InstallDir                   => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    BackupPCUserVerify           => {struct => 'SCALAR',
+                                     type   => 'BOOLEAN', },
+
+    SmbShareName                 => {struct => 'ARRAY',
+                                     type   => 'STRING', },
+
+    SmbShareUserName             => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    SmbSharePasswd               => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    TarShareName                 => {struct => 'ARRAY',
+                                     type   => 'STRING', },
+
+    FullPeriod                   => {struct => 'SCALAR',
+                                     type   => 'FLOAT', },
+
+    IncrPeriod                   => {struct => 'SCALAR',
+                                     type   => 'FLOAT', },
+
+    FullKeepCnt                  => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    FullKeepCntMin               => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    FullAgeMax                   => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    IncrKeepCnt                  => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    IncrKeepCntMin               => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    IncrAgeMax                   => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    IncrFill                     => {struct => 'SCALAR',
+                                     type   => 'BOOLEAN', },
+
+    RestoreInfoKeepCnt           => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    BackupFilesOnly              => {struct => 'HASHOFARRAY',
+                                     type   => 'STRING', },
+
+    BackupFilesExclude           => {struct => 'HASHOFARRAY',
+                                     type   => 'STRING', },
+
+    BlackoutBadPingLimit         => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    BlackoutGoodCnt              => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    BlackoutHourBegin            => {struct => 'SCALAR',
+                                     type   => 'FLOAT', },
+
+    BlackoutHourEnd              => {struct => 'SCALAR',
+                                     type   => 'FLOAT', },
+
+    BlackoutWeekDays             => {struct => 'ARRAY',
+                                     type   => 'INT', },
+
+    XferMethod                   => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    SmbClientPath                => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    SmbClientArgs                => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    TarClientCmd                 => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    TarFullArgs                  => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    TarIncrArgs                  => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    TarClientRestoreCmd          => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    TarClientPath                => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    SshPath                      => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    NmbLookupPath                => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    FixedIPNetBiosNameCheck      => {struct => 'SCALAR',
+                                     type   => 'BOOLEAN', },
+
+    PingPath                     => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    PingArgs                     => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CompressLevel                => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    PingMaxMsec                  => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    SmbClientTimeout             => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    MaxOldPerPCLogFiles          => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    SendmailPath                 => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    EMailNotifyMinDays           => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    EMailFromUserName            => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    EMailAdminUserName           => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    EMailNoBackupEverMesg        => {struct => 'SCALAR',
+                                     type   => 'MEMO', },
+
+    EMailNotifyOldBackupDays     => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    EMailNoBackupRecentMesg      => {struct => 'SCALAR',
+                                     type   => 'MEMO', },
+
+    EMailNotifyOldOutlookDays    => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    EMailOutlookBackupMesg       => {struct => 'SCALAR',
+                                     type   => 'MEMO', },
+
+    CgiAdminUserGroup            => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiAdminUsers                => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    Language                     => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiUserHomePageCheck         => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiUserUrlCreate             => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiDateFormatMMDD            => {struct => 'SCALAR',
+                                     type   => 'BOOLEAN', },
+
+    CgiNavBarAdminAllHosts       => {struct => 'SCALAR',
+                                     type   => 'BOOLEAN', },
+
+    CgiHeaderFontType            => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiHeaderFontSize            => {struct => 'SCALAR',
+                                     type   => 'INT', },
+
+    CgiNavBarBgColor             => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiHeaderBgColor             => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiHeaders                   => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiImageDir                  => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+    CgiImageDirURL               => {struct => 'SCALAR',
+                                     type   => 'STRING', },
+
+);
+
+1;
diff --git a/lib/BackupPC/Config/Db.pm b/lib/BackupPC/Config/Db.pm
new file mode 100644 (file)
index 0000000..5343fa6
--- /dev/null
@@ -0,0 +1,435 @@
+package BackupPC::Config::Db;
+
+use base 'BackupPC::Config';
+use warnings;
+use strict;
+
+use DBI;
+our $SELF;
+
+sub BackupInfoRead
+{
+    my($self, $client) = @_;
+
+    # ORDER BY is important! BackupPC_dump expects list to be sorted
+    my $cmd = "SELECT " . join(', ', @{ $self->{BackupFields} })
+        . " FROM Backup WHERE client = '$client' ORDER BY num";
+    my $sth = $self->{dbh}->prepare($cmd);
+
+    $sth->execute;
+    my($row, @backups);
+
+NUM:
+    while ($row = $sth->fetchrow_hashref) {
+        $backups[@backups] = { %$row };
+    }
+
+    return @backups;
+}
+
+sub BackupInfoWrite
+{
+    my($self, $client, @backups) = @_;
+    
+    #BackupPC_dump passes an array containing all backup records, so we must
+    #1) figure out which ones aren't in the database and add them; then
+    #2) delete records in the database that weren't passed
+    
+    # get a hash of currently existing backup nums from database
+    my %current = map {$_, 1}
+        @{ $self->{dbh}->selectcol_arrayref("SELECT num FROM Backup") };
+        
+    my %textFields = map {$_, 1} 'client', @{ $self->{BackupTextFields} };
+    
+    my($num, $cmd, $sth);
+
+NUM:
+    foreach my $backup (@backups) {
+        $num = $backup->{num};
+        
+        if (defined $current{$num}) {
+            #it's in the database as well as @backups; delete it from hash
+            delete $current{$num};
+            
+        } else {
+            #it's not in database yet, so add it
+            $cmd = "INSERT Backup (client, " . join(', ', @{ $self->{BackupFields} })
+                . ") VALUES ('$client', " . join(', ',
+                map {(defined $textFields{$_})? "'$backup->{$_}'" : $backup->{$_}}
+                @{ $self->{BackupFields} }) . ")";
+        
+            $self->{dbh}->prepare($cmd)->execute;
+        }
+
+    }
+
+    # any remaining items in %current should be discarded
+    if (%current) {
+        $cmd = "DELETE FROM Backup WHERE num IN (" . join(', ', sort keys %current)
+            . ")";
+        $self->{dbh}->prepare($cmd)->execute;
+    }
+
+}
+
+
+# See comments in "Backup" subs, above
+sub RestoreInfoRead
+{
+    my($self, $client) = @_;
+
+    # ORDER BY is important! BackupPC_dump expects list to be sorted
+    my $cmd = "SELECT " . join(', ', @{ $self->{RestoreFields} })
+        . " FROM Restore WHERE client = '$client' ORDER BY num";
+    my $sth = $self->{dbh}->prepare($cmd);
+
+    $sth->execute;
+    my($row, @restores);
+
+NUM:
+    while ($row = $sth->fetchrow_hashref) {
+        $restores[@restores] = { %$row };
+    }
+
+    return @restores;
+}
+
+
+# See comments in "Backup" subs, above
+sub RestoreInfoWrite
+{
+    my($self, $client, @restores) = @_;
+    
+    my %current = map {$_, 1}
+        @{ $self->{dbh}->selectcol_arrayref("SELECT num FROM Restore") };
+        
+    my %textFields = map {$_, 1} 'client', @{ $self->{RestoreTextFields} };
+    
+    my($num, $cmd, $sth);
+
+NUM:
+    foreach my $restore (@restores) {
+        $num = $restore->{num};
+        
+        if (defined $current{$num}) {
+            delete $current{$num};
+            
+        } else {
+            $cmd = "INSERT Restore (client, " . join(', ', @{ $self->{RestoreFields} })
+                . ") VALUES ('$client', " . join(', ',
+                map {(defined $textFields{$_})? "'$restore->{$_}'" : $restore->{$_}}
+                @{ $self->{RestoreFields} }) . ")";
+        
+            $self->{dbh}->prepare($cmd)->execute;
+        }
+
+    }
+
+    if (%current) {
+        $cmd = "DELETE FROM Restore WHERE num IN (" . join(', ', sort keys %current)
+            . ")";
+        $self->{dbh}->prepare($cmd)->execute;
+    }
+
+}
+
+sub HostInfoRead {
+    my($self, $oneClient) = @_;
+    
+    my $cmd = "SELECT client AS host, dhcp, user, moreUsers FROM Client";
+    my $sth = $self->{dbh}->prepare($cmd);
+    
+    $sth->execute;
+    my($row, $client, %clients);
+    
+CLIENT:
+    while ($row = $sth->fetchrow_hashref) {
+        $client = $row->{host};
+    
+        if (defined $oneClient) {
+            next CLIENT unless $oneClient eq $client;
+            $clients{$client} = {%$row};
+            return \%clients;
+        }
+    
+        $clients{$client} = {%$row};
+    }
+    
+    return \%clients;
+
+}
+
+
+#TODO: Replace w/ Db version!!
+sub ConfigRead
+{
+    my($self, $host) = @_;
+    my($ret, $mesg, $config, @configs);
+    
+    our %Conf;
+
+    $self->{Conf} = ();
+    push(@configs, "$self->{TopDir}/conf/config.pl");
+    push(@configs, "$self->{TopDir}/pc/$host/config.pl")
+            if ( defined($host) && -f "$self->{TopDir}/pc/$host/config.pl" );
+    foreach $config ( @configs ) {
+        %Conf = ();
+        if ( !defined($ret = do $config) && ($! || $@) ) {
+            $mesg = "Couldn't open $config: $!" if ( $! );
+            $mesg = "Couldn't execute $config: $@" if ( $@ );
+            $mesg =~ s/[\n\r]+//;
+            return $mesg;
+        }
+        %{$self->{Conf}} = ( %{$self->{Conf} || {}}, %Conf );
+    }
+    
+    #$mesg = $self->CheckConfigInfo;
+    #return $mesg if $mesg;
+    
+    return if ( !defined($self->{Conf}{Language}) );
+    
+    my $langFile = "$self->{LibDir}/BackupPC/Lang/$self->{Conf}{Language}.pm";
+    
+    if ( !defined($ret = do $langFile) && ($! || $@) ) {
+        $mesg = "Couldn't open language file $langFile: $!" if ( $! );
+        $mesg = "Couldn't execute language file $langFile: $@" if ( $@ );
+        $mesg =~ s/[\n\r]+//;
+        return $mesg;
+    }
+    
+    our %Lang;
+    $self->{Lang} = \%Lang;
+    
+    return;
+}
+
+our %gConfigWriteHandler = (SCALAR      => \&_ConfigWriteScalar,
+                            ARRAY       => \&_ConfigWriteArray,
+                            HASH        => \&_ConfigWriteHash,
+                            ARRAYOFHASH => \&_ConfigWriteArrayOfHash,
+                            HASHOFARRAY => \&_ConfigWriteHashOfArray,
+                           );
+
+our %gConfigTypeField; # will be defined by database-specific Config module
+
+sub ConfigWrite {
+    my($self, $client) = @_;
+    $SELF = $self;
+    my $dbh = $self->{dbh};
+    
+    $dbh->{RaiseError} = 0;
+    my($cmd, $sth);
+    
+    $cmd = "DELETE FROM Config WHERE client = '~~$client'";
+    $sth = $dbh->prepare($cmd) or return "$cmd\n". $dbh->errstr;
+    $sth->execute or return "$cmd\n". $dbh->errstr;
+    
+    $cmd = "UPDATE Config SET client = '~~$client' WHERE client = '$client'";
+    $sth = $dbh->prepare($cmd) or return "$cmd\n". $dbh->errstr;
+    $sth->execute or return "$cmd\n". $dbh->errstr;
+    
+    my($attr, $val, $def, $handler, $mesg);
+    
+    foreach $attr (sort keys %{ $self->{Conf} }) {
+        $val = $self->{Conf}->{$attr};
+        $def = $self->{ConfigDef}->{$attr};
+        
+        $handler = $gConfigWriteHandler{$def->{struct}};
+        $mesg = &$handler($dbh, $def, $client, $attr, $val);
+        return $mesg if $mesg;
+    }
+
+    
+    $cmd = "DELETE FROM Config WHERE client = '~~$client'";
+    $sth = $dbh->prepare($cmd) or return "$cmd\n". $dbh->errstr;
+    $sth->execute or return "$cmd\n". $dbh->errstr;
+    
+    $self->{dbh}->{RaiseError} = 1;
+    
+    return;
+}
+
+sub _ConfigWriteScalar {
+    my($dbh, $def, $client, $attr, $val) = @_;
+    $SELF->Debug("SCALAR $val") if $attr eq 'BackupFilesOnly';
+    return if !defined $val;
+    
+    my $ref = ref $val;
+    
+    if ($ref) {
+        return "Expected $attr to be SCALAR, but got $ref";
+    }
+    
+    &_WriteConfigRow($dbh, $client, $attr, -1, '', $def->{type}, $val)
+}
+
+sub _ConfigWriteArray {
+    my($dbh, $def, $client, $attr, $val, $key) = @_;
+    $SELF->Debug("ARRAY $val, $key") if $attr eq 'BackupFilesOnly';
+    return if !defined $val;
+
+    $key = '' unless defined $key;
+    my $ref = ref $val;
+    
+    if (!$ref) {
+        #expecting ARRAY, got string -- implicit convert
+        $val = [ $val ];
+    } elsif ($ref ne 'ARRAY') {
+        $attr = "$attr\{$key}" if $key ne '';
+        return "Expected $attr to be ARRAY, but got $ref";
+    }
+    
+    my $subscript = 0;
+    my $item;
+    my $type = $def->{type};
+    
+    foreach $item (@$val) {
+        &_WriteConfigRow($dbh, $client, $attr, $subscript++,
+                         $key, $type, $item)
+    }
+}
+
+sub _ConfigWriteHash {
+    my($dbh, $def, $client, $attr, $val, $subscript) = @_;
+    $SELF->Debug("HASH $val") if $attr eq 'BackupFilesOnly';
+    return if !defined $val;
+
+    $subscript = -1 unless defined $subscript;
+    my $ref = ref $val;
+    
+    if (!$ref) {
+        #expecting HASH, got string -- implicit convert
+        $val = { '*' => $val };
+    } elsif ($ref ne 'HASH') {
+        $attr = "$attr\[$subscript]" if $subscript != -1;
+        return "Expected $attr to be HASH, but got $ref";
+    }
+    
+    my($key, $item);
+    my $type = $def->{type};
+    
+    # If 'type' is a hash ref, this means the attribute's subvalue type
+    # depends on what its corresponding key is. In that case, we set
+    # $thisType for each iteration; otherwise, we leave it set to 'type'
+    my $typeByKey = ref $type;
+    my $thisType = $type;
+    
+    foreach $key (sort keys %$val) {
+        $item = $val->{$key};
+        
+        if ($typeByKey) {
+            $thisType = $type->{$key};
+            
+            if (!defined $thisType) {
+                return "Don't know how to handle subvalue $key for $attr";
+            }
+        }
+        
+        &_WriteConfigRow($dbh, $client, $attr, $subscript,
+                         $key, $thisType, $item)
+    }
+}
+
+sub _ConfigWriteArrayOfHash {
+    my($dbh, $def, $client, $attr, $val) = @_;
+    return if !defined $val;
+
+    my $ref = ref $val;
+    
+    if (!$ref) {
+        #expecting ARRAY, got string -- implicit convert
+        $val = [ $val ];
+    } elsif ($ref ne 'ARRAY') {
+        return "Expected $attr to be ARRAY, but got $ref";
+    }
+    
+    my $subscript = 0;
+    my $item;
+    
+    foreach $item (@$val) {
+        &_ConfigWriteHash($dbh, $def, $client, $attr,
+                          $item, $subscript++);
+    }
+}
+
+sub _ConfigWriteHashOfArray {
+    my($dbh, $def, $client, $attr, $val) = @_;
+    $SELF->Debug("HASHOFARRAY $val") if $attr eq 'BackupFilesOnly';
+    return if !defined $val;
+
+    my $ref = ref $val;
+    
+    if (!$ref) {
+        #expecting HASH, got string -- implicit convert
+        $val = { '*' => $val };
+    } elsif ($ref ne 'HASH') {
+        return "Expected $attr to be HASH, but got $ref";
+    }
+    
+    my($key, $item);
+    
+    foreach $key (sort keys %$val) {
+        $item = $val->{$key};
+        &_ConfigWriteArray($dbh, $def, $client, $attr,
+                           $item, $key);
+    }
+    
+}
+
+sub _WriteConfigRow {
+    my($dbh, $client, $attr, $subscript, $key, $type, $val) = @_;
+    
+    defined $gConfigTypeField{$type}
+        or return "Unknown ConfigDef type '$type' ($attr); aborting";
+        
+    my($confType, $field, %fields);
+    
+    while(($confType, $field) = each %gConfigTypeField) {
+        if ($confType eq $type) {
+            # this is the correct field for value of interest,
+            # so copy and format it
+            $fields{$field} = ($confType =~ /^(STRING|MEMO)$/)?
+                $dbh->quote($val) : $val;
+        } else {
+            $fields{$field} = 'NULL';
+        }
+    }
+    
+    $fields{'client'} = $dbh->quote($client);
+    $fields{'clientGroup'} = "''"; #TODO: add group logic
+    $fields{'attribute'} = $dbh->quote($attr);
+    $fields{'subscript'} = $subscript;
+    $fields{'hashKey'} = $dbh->quote($key);
+    
+    my @fields = sort keys %fields;
+    my @values = map { $fields{ $_ } } @fields;
+    
+    my $cmd = "INSERT Config (" . join(', ', @fields) . ")\nVALUES ("
+        . join(', ', @values) . ")";
+    
+    my $sth = $dbh->prepare($cmd) or return "$cmd\n\n" . $dbh->errstr;
+    $sth->execute or return "$cmd\n\n". $dbh->errstr;
+
+    return;
+}
+
+#TODO: Replace w/ Db version!!
+#
+# Return the mtime of the config file
+#
+sub ConfigMTime
+{
+    my($self) = @_;
+    return (stat("$self->{TopDir}/conf/config.pl"))[9];
+}
+
+
+
+sub DESTROY {
+    my($self) = @_;
+
+    $self->{dbh}->disconnect if defined $self->{dbh};
+}
+
+
+1;
diff --git a/lib/BackupPC/Config/Db/MySQL.pm b/lib/BackupPC/Config/Db/MySQL.pm
new file mode 100644 (file)
index 0000000..dfb21cb
--- /dev/null
@@ -0,0 +1,111 @@
+package BackupPC::Config::Db::MySQL;
+
+use base 'BackupPC::Config::Db';
+use warnings;
+use strict;
+
+use DBI;
+use DBD::mysql;
+
+%BackupPC::Config::Db::gConfigTypeField =
+    (BOOLEAN   => 'valueBit',
+     INT       => 'valueInt',
+     FLOAT     => 'valueFloat',
+     STRING    => 'valueString',
+     MEMO      => 'valueMemo',
+    );
+
+sub ConnectData {
+   my($self) = @_;
+
+   my $dsn = 'DBI:mysql:database=backuppc;host=reagan';
+   $self->{dbh} = DBI->connect($dsn, 'root', undef, {RaiseError => 1,
+                               AutoCommit => 1,});
+}
+
+sub ConfigMTime {
+   my($self) = @_;
+return time();
+   my $cmd = "SHOW TABLE STATUS LIKE 'Config'";
+   my $sth = $self->{dbh}->prepare($cmd);
+
+   $sth->execute;
+   my $row = $sth->fetchrow_hashref || return time();
+   my $mtime;
+
+   if (defined($mtime = $row->{'Update_time'})) {
+      $mtime =~ m/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/
+        || return time();
+      return &Date_SecsSince1970($2,$3,$1,$4,$5,$6);
+   } else {
+      return time();
+   }
+}
+
+sub HostsMTime {
+   my($self) = @_;
+return time();
+   my $cmd = "SHOW TABLE STATUS LIKE 'Client'";
+   my $sth = $self->{dbh}->prepare($cmd);
+
+   $sth->execute;
+   my $row = $sth->fetchrow_hashref || return time();
+   my $mtime;
+
+   if (defined($mtime = $row->{'Update_time'})) {
+      return &Date_SecsSince1970($mtime);
+   } else {
+      return time();
+   }
+}
+
+# Date subs borrowed from Date::Manip.
+# Copyright (c) 1995-2001 Sullivan Beck.  All rights reserved.
+# This program is free software; you can redistribute it and/or modify it
+# under the same terms as Perl itself.
+
+sub Date_SecsSince1970 {
+  my($mysqlDate) = @_;
+  $mysqlDate =~ m/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/
+    || return time();
+  my($y,$m,$d,$h,$mn,$s) = ($1,$2,$3,$4,$5,$6);
+  my($sec_now,$sec_70,$Ny,$N4,$N100,$N400,$dayofyear,$days)=();
+  my($cc,$yy)=();
+
+  $y=~ /(\d{2})(\d{2})/;
+  ($cc,$yy)=($1,$2);
+
+  $Ny=$y;
+
+  $N4=($Ny-1)/4 + 1;
+  $N4=0         if ($y==0);
+
+  $N100=$cc + 1;
+  $N100--       if ($yy==0);
+  $N100=0       if ($y==0);
+
+  $N400=($N100-1)/4 + 1;
+  $N400=0       if ($y==0);
+
+  my(@days) = ( 0, 31, 59, 90,120,151,181,212,243,273,304,334,365);
+  my($ly)=0;
+  $ly=1  if ($m>2 && &Date_LeapYear($y));
+
+  $dayofyear=$days[$m-1]+$d+$ly;
+  $days= $Ny*365 + $N4 - $N100 + $N400 + $dayofyear;
+  $sec_now=($days-1)*24*3600 + $h*3600 + $mn*60 + $s;
+  $sec_70 =62167219200;
+  return ($sec_now-$sec_70);
+}
+
+sub Date_LeapYear {
+  my($y)=@_;
+  return 0 if $y % 4;
+  return 1 if $y % 100;
+  return 0 if $y % 400;
+  return 1;
+}
+
+
+
+1;
diff --git a/lib/BackupPC/Config/Text.pm b/lib/BackupPC/Config/Text.pm
new file mode 100644 (file)
index 0000000..8ee2927
--- /dev/null
@@ -0,0 +1,198 @@
+package BackupPC::Config::Text;
+
+use warnings;
+use strict;
+use Fcntl qw/:flock/;
+
+use base 'BackupPC::Config';
+
+sub BackupInfoRead
+{
+    my($self, $host) = @_;
+    local(*BK_INFO, *LOCK);
+    my(@Backups);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$self->{TopDir}/pc/$host/LOCK");
+    if ( open(BK_INFO, "$self->{TopDir}/pc/$host/backups") ) {
+        while ( <BK_INFO> ) {
+            s/[\n\r]+//;
+            next if ( !/^(\d+\t(incr|full)[\d\t]*$)/ );
+            $_ = $1;
+            @{$Backups[@Backups]}{@{$self->{BackupFields}}} = split(/\t/);
+        }
+        close(BK_INFO);
+    }
+    close(LOCK);
+    return @Backups;
+}
+
+sub BackupInfoWrite
+{
+    my($self, $host, @Backups) = @_;
+    local(*BK_INFO, *LOCK);
+    my($i);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$self->{TopDir}/pc/$host/LOCK");
+    unlink("$self->{TopDir}/pc/$host/backups.old")
+                if ( -f "$self->{TopDir}/pc/$host/backups.old" );
+    rename("$self->{TopDir}/pc/$host/backups",
+           "$self->{TopDir}/pc/$host/backups.old")
+                if ( -f "$self->{TopDir}/pc/$host/backups" );
+    if ( open(BK_INFO, ">$self->{TopDir}/pc/$host/backups") ) {
+        for ( $i = 0 ; $i < @Backups ; $i++ ) {
+            my %b = %{$Backups[$i]};
+            printf(BK_INFO "%s\n", join("\t", @b{@{$self->{BackupFields}}}));
+        }
+        close(BK_INFO);
+    }
+    close(LOCK);
+}
+
+sub RestoreInfoRead
+{
+    my($self, $host) = @_;
+    local(*RESTORE_INFO, *LOCK);
+    my(@Restores);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$self->{TopDir}/pc/$host/LOCK");
+    if ( open(RESTORE_INFO, "$self->{TopDir}/pc/$host/restores") ) {
+        while ( <RESTORE_INFO> ) {
+            s/[\n\r]+//;
+            next if ( !/^(\d+.*)/ );
+            $_ = $1;
+            @{$Restores[@Restores]}{@{$self->{RestoreFields}}} = split(/\t/);
+        }
+        close(RESTORE_INFO);
+    }
+    close(LOCK);
+    return @Restores;
+}
+
+sub RestoreInfoWrite
+{
+    my($self, $host, @Restores) = @_;
+    local(*RESTORE_INFO, *LOCK);
+    my($i);
+
+    flock(LOCK, LOCK_EX) if open(LOCK, "$self->{TopDir}/pc/$host/LOCK");
+    unlink("$self->{TopDir}/pc/$host/restores.old")
+                if ( -f "$self->{TopDir}/pc/$host/restores.old" );
+    rename("$self->{TopDir}/pc/$host/restores",
+           "$self->{TopDir}/pc/$host/restores.old")
+                if ( -f "$self->{TopDir}/pc/$host/restores" );
+    if ( open(RESTORE_INFO, ">$self->{TopDir}/pc/$host/restores") ) {
+        for ( $i = 0 ; $i < @Restores ; $i++ ) {
+            my %b = %{$Restores[$i]};
+            printf(RESTORE_INFO "%s\n",
+                        join("\t", @b{@{$self->{RestoreFields}}}));
+        }
+        close(RESTORE_INFO);
+    }
+    close(LOCK);
+}
+
+sub ConfigRead
+{
+    my($self, $host) = @_;
+    my($ret, $mesg, $config, @configs);
+    
+    our %Conf;
+
+    $self->{Conf} = ();
+    push(@configs, "$self->{TopDir}/conf/config.pl");
+    push(@configs, "$self->{TopDir}/pc/$host/config.pl")
+            if ( defined($host) && -f "$self->{TopDir}/pc/$host/config.pl" );
+    foreach $config ( @configs ) {
+        %Conf = ();
+        if ( !defined($ret = do $config) && ($! || $@) ) {
+            $mesg = "Couldn't open $config: $!" if ( $! );
+            $mesg = "Couldn't execute $config: $@" if ( $@ );
+            $mesg =~ s/[\n\r]+//;
+            return $mesg;
+        }
+        %{$self->{Conf}} = ( %{$self->{Conf} || {}}, %Conf );
+    }
+    
+    #$mesg = $self->CheckConfigInfo;
+    #return $mesg if $mesg;
+    
+    return if ( !defined($self->{Conf}{Language}) );
+    
+    my $langFile = "$self->{LibDir}/BackupPC/Lang/$self->{Conf}{Language}.pm";
+    
+    if ( !defined($ret = do $langFile) && ($! || $@) ) {
+        $mesg = "Couldn't open language file $langFile: $!" if ( $! );
+        $mesg = "Couldn't execute language file $langFile: $@" if ( $@ );
+        $mesg =~ s/[\n\r]+//;
+        return $mesg;
+    }
+    
+    our %Lang;
+    $self->{Lang} = \%Lang;
+    
+    return;
+}
+
+
+#
+# Return the mtime of the config file
+#
+sub ConfigMTime
+{
+    my($self) = @_;
+    return (stat("$self->{TopDir}/conf/config.pl"))[9];
+}
+
+#
+# Returns information from the host file in $self->{TopDir}/conf/hosts.
+# With no argument a ref to a hash of hosts is returned.  Each
+# hash contains fields as specified in the hosts file.  With an
+# argument a ref to a single hash is returned with information
+# for just that host.
+#
+sub HostInfoRead
+{
+    my($self, $host) = @_;
+    my(%hosts, @hdr, @fld);
+    local(*HOST_INFO);
+
+    if ( !open(HOST_INFO, "$self->{TopDir}/conf/hosts") ) {
+        print(STDERR $self->timeStamp,
+                     "Can't open $self->{TopDir}/conf/hosts\n");
+        return {};
+    }
+    while ( <HOST_INFO> ) {
+        s/[\n\r]+//;
+        s/#.*//;
+        s/\s+$//;
+        next if ( /^\s*$/ || !/^([\w\.-]+\s+.*)/ );
+        @fld = split(/\s+/, $1);
+        if ( @hdr ) {
+            if ( defined($host) ) {
+                next if ( lc($fld[0]) ne $host );
+                @{$hosts{lc($fld[0])}}{@hdr} = @fld;
+               close(HOST_INFO);
+                return \%hosts;
+            } else {
+                @{$hosts{lc($fld[0])}}{@hdr} = @fld;
+            }
+        } else {
+            @hdr = @fld;
+        }
+    }
+    close(HOST_INFO);
+    return \%hosts;
+}
+
+#
+# Return the mtime of the hosts file
+#
+sub HostsMTime
+{
+    my($self) = @_;
+    return (stat("$self->{TopDir}/conf/hosts"))[9];
+}
+
+
+
+1;