# 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;