# Copyright 2007, 2008 Robert ("Bob") Igo of StormLogic, LLC and mythic.tv. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . package Tweaker::Script; use strict; use DBI; use Switch; use Tweaker::Definitions; # To install the above modules: # -- # sudo apt-get install libdbi-perl liblog-log4perl-perl # sudo cpan install Getopt::Lucid List::Member # choose the defaults for all options, agree to install all dependencies use Getopt::Lucid qw( :all ); # see http://search.cpan.org/~dagolden/Getopt-Lucid-0.16/lib/Getopt/Lucid.pm for usage details use Log::Log4perl qw(get_logger); # see http://search.cpan.org/~mschilli/Log-Log4perl-1.14/lib/Log/Log4perl.pm for usage details # http://www.perl.com/pub/a/2002/09/11/log4perl.html is highly recommended as well use List::Member; my $dbh; # the database we connect to my $sth; # my $TWEAKER_ROOT; my @known_options; # Print advice on usage and invocation. sub help () { my $logger = get_logger('tweaker.script'); $logger->fatal("USAGE:\n$0 [--check Option] [--implement Option] [--poll Option] [--iterations] [--help]"); $logger->fatal("Valid values for Option are: ", join(" ",@known_options)); $logger->fatal("Only one of the above parameters may be passed."); $logger->fatal("The --check and --iterations parameters are not yet supported."); exit; } sub execute_shell_command { my($command) = @_; my $results=""; my $logger = get_logger('tweaker.script'); open(SHELL, "$command 2>&1 |"); while() { $results=$results."".$_; } close(SHELL); chop($results); $logger->debug("Command '$command' produced output '$results'"); return $results; } # Simple way to get the value of an environment variable from the executing shell. sub get_environment_variable { my($envvar) = @_; open(SHELL, "echo $envvar|"); while() { chop; return $_; } close(SHELL); } sub get_mythtv_connection_string { # we want something like mythconverg:localhost my $dbname = ""; my $dbhostname = ""; open(MYSQLTXT, "< /usr/share/mythtv/mysql.txt"); while() { if (/DBName=(.*)/) { $dbname=$1; } elsif (/DBHostName=(.*)/) { $dbhostname=$1; } } close(MYSQLTXT); return "$dbname:$dbhostname"; } sub get_mythtv_authentication { # we want something like ['root', ''] my $dbusername = ""; my $dbpassword = ""; open(MYSQLTXT, "< /usr/share/mythtv/mysql.txt"); while() { if (/DBUserName=(.*)/) { $dbusername=$1; } elsif (/DBPassword=(.*)/) { $dbpassword=$1; } } close(MYSQLTXT); return ($dbusername, $dbpassword); } # Database routines. sub connect_to_db { my ($db) = @_; my $logger = get_logger('tweaker.script'); my ($dbusername, $dbpassword) = get_mythtv_authentication(); if (!($dbh = DBI->connect($db, $dbusername, $dbpassword))) { $logger->error("Couldn't connect to database: ", DBI->errstr); return -1; } return 1; } sub disconnect_from_db { $dbh->disconnect; } sub do_query { my ($query) = @_; my $logger = get_logger('tweaker.script'); my $rv=""; $logger->debug("Processing statement: ", $query); if (!($sth = $dbh->prepare($query))) { $logger->error("Couldn't prepare statement: ", $dbh->errstr); return -1; } $rv = $sth->execute(); # Returns an integer when rows were affected; returns -1 when there's an # error; returns 0E0 (true) when no error but no rows affected. if (!$rv) { $logger->error("Couldn't execute statement: ", $sth->errstr); return -1; } return $rv; } # Make sure the option passed to this script is handled. sub validate_option { my($option) = @_; if (!(member($option, @known_options) + 1)) { my $logger = get_logger('tweaker.script'); $logger->fatal("Option '$option' is not known to $0."); $logger->fatal("Valid Options are: ", join(", ",@known_options)); exit -1; } return 1; } # Prints out each option that the script handles, separated by '|'. # This allows for a minimal .tcf entry. sub get_options { print join("|",@known_options),"\n"; } # Prints out the passed Recommendation Level first, followed by any other # strings, separated by '|'. This allows the author of a Tweaker Script to # return explanatory information along with a Recommendation Level. sub recommendation_level { print join("|",@_),"\n"; } sub process_parameters () { # Accept these parameters: # -- my @parameters = ( Switch("help")->anycase, Param("check")->anycase, # Requirement 1.1 Param("implement")->anycase, # Requirement 1.2 Param("poll")->anycase, # Requirement 1.3 Switch("iterations")->anycase, # Requirement 1.4 Switch("getoptions")->anycase # Requirement 1.5 ); my $opt = Getopt::Lucid->getopt( \@parameters ); if (!(my $TWEAKER_ROOT = get_environment_variable("\$TWEAKER_ROOT"))) { Log::Log4perl->easy_init(); my $logger = get_logger(); $logger->fatal("ERROR: \$TWEAKER_ROOT environment variable is not set."); exit -1; } else { my $log4perl_conf = "$TWEAKER_ROOT/log4perl.conf"; Log::Log4perl::init_and_watch($log4perl_conf,10); my $logger = get_logger('tweaker.script'); $logger->info("\$TWEAKER_ROOT is '$TWEAKER_ROOT'."); } if ($opt->get_help > 0) { help; } my $check = $opt->get_check; my $implement = $opt->get_implement; my $poll = $opt->get_poll; my $iterations = $opt->get_iterations; my $getoptions = $opt->get_getoptions; # Requirement 1.6 if ($check) { if ($implement || $poll || $iterations || $getoptions) { help; } validate_option($check); # exits with an error if option is invalid check_option($check); # Requirement 1.1 } elsif ($implement) { if ($poll || $iterations || $getoptions) { help; } validate_option($implement); # exits with an error if option is invalid implement_option($implement); # Requirement 1.2 } elsif ($poll) { if ($iterations || $getoptions) { help; } validate_option($poll); # exits with an error if option is invalid poll_options($poll); # Requirement 1.3 } elsif ($iterations) { if ($getoptions) { help; } #count_iterations; # Requirement 1.4 } elsif ($getoptions) { get_options; # Requirement 1.5 } else { help; } } sub set_known_options { my(@options) = @_; @known_options = @_; } # These entries may or may not already exist. First, try updating them, and if that fails, insert them. # Pass in array references for setfields and checkfields. Each must # reference an array of lists, where each list is a key, value pair, e.g. # [["data", "somedata"], ["name", "skippy"]] sub change_or_make_entry { my($table, $setfields, $checkfields) = @_; my $query_string = "UPDATE $table SET "; my $fields=""; foreach my $sets (@$setfields) { if ($fields) { $fields = $fields . ", "; } $fields = $fields . "@$sets[0]='@$sets[1]' "; } $query_string = $query_string . $fields . "WHERE "; my $checkstring=""; foreach my $checks (@$checkfields) { if ($checkstring) { $checkstring = $checkstring . "AND "; } $checkstring = $checkstring . "@$checks[0]='@$checks[1]' "; } $query_string = $query_string . $checkstring; my $rv = do_query($query_string); if (($rv == 0E0) || ($rv < 1)) { # UPDATE didn't apply; do an insert my $fields=""; my $values=""; foreach my $sets (@$setfields) { if ($fields) { $fields = $fields . ", "; $values = $values . ", "; } $fields = $fields . "@$sets[0]"; $values = $values . "'@$sets[1]'"; } foreach my $sets (@$checkfields) { if ($fields) { $fields = $fields . ", "; $values = $values . ", "; } $fields = $fields . "@$sets[0]"; $values = $values . "'@$sets[1]'"; } $query_string = "INSERT INTO $table (". $fields . ") VALUES (" . $values . ")"; $rv = do_query($query_string); } return $rv; } # We update so many entries in the settings table that a subroutine makes coding and readability easier. sub change_or_make_setting { my($value, $data) = @_; return(change_or_make_entry("settings", [["data", $data]], [["value", $value]])); } # Benchmark-driven tests for low, medium, or high "performance" often look the same. # If your test falls into this pattern, you can use this subroutine to simplify your # Tweaker Script's poll_options subroutine. # NOTE: This only handles options for low, medium, or high right now. # NOTE: You don't have to use this! Only use it if your poll_options subroutine # would look like this anyway. Don't shoehorn it to fit! sub threshold_test { my($option, $benchmark_number, $name_of_benchmarked_device, $low_threshold, $medium_threshold, $high_threshold) = @_; # e.g. ("medium", 512, "video card", 350, 425, 500) my $logger = get_logger('tweaker.script'); $logger->debug("Threshold test for option '$option' ($name_of_benchmarked_device) with benchmark of $benchmark_number, where: low = $low_threshold, medium = $medium_threshold, high = $high_threshold"); switch ($option) { case "low" { if ($benchmark_number <= $low_threshold) { recommendation_level("recommended", "Your $name_of_benchmarked_device seems to be unable to handle higher usage than this."); } elsif ($benchmark_number <= $medium_threshold) { recommendation_level("optional", "Your $name_of_benchmarked_device seems to be able to handle higher usage than this, but select this option if you want to reduce overall $name_of_benchmarked_device usage."); } elsif ($benchmark_number >= $high_threshold) { recommendation_level("optional", "Your $name_of_benchmarked_device seems to be quite capable of this setting, but select this option if you want to reduce overall $name_of_benchmarked_device usage."); } } case "medium" { if ($benchmark_number <= $low_threshold) { recommendation_level("inadvisable", "Your $name_of_benchmarked_device seems to be unable to handle this usage level, but you are free to try."); } elsif ($benchmark_number <= $medium_threshold) { recommendation_level("recommended", "Your $name_of_benchmarked_device seems to be unable to handle higher usage than this."); } elsif ($benchmark_number >= $high_threshold) { recommendation_level("optional", "Your $name_of_benchmarked_device seems to be able to handle higher usage than this, but select this option if you want to reduce overall $name_of_benchmarked_device usage."); } } case "high" { if ($benchmark_number <= $low_threshold) { recommendation_level("inadvisable", "Your $name_of_benchmarked_device seems to be unable to handle this usage level, and it is not recommended that you try."); } elsif ($benchmark_number <= $medium_threshold) { recommendation_level("inadvisable", "Your $name_of_benchmarked_device seems to be unable to handle this usage level, but you are free to try."); } elsif ($benchmark_number >= $high_threshold) { recommendation_level("recommended", "Your $name_of_benchmarked_device seems to be quite capable of this setting."); } } } } 1;