#!/usr/local/bin/perl
#
# author: Vincent DiBartolo (vdibart@nodroidsallowed.com)
# description: Get some data (e.g. a MySQL dump), compare it to the previous one and email it if
# the sha1 hash is different.
#
#TODO: improve error handling when cookie is expired, etc.
#
use strict;
use MIME::Lite;
use Digest::SHA1 qw(sha1_base64);
use Getopt::Std;
#----------------------------------- JOBS TO RUN -----------------------------------#
# hash of different sets of data to run through
my (%jobs);
# command for wget
my ($wget) = "wget --cookies=off --quiet --output-document /dev/stdout";
# Live MySQL databases
$jobs{"mysql"}{"command"} = "mysqldump --opt --hex-blob --all-databases --user=scott --password=tiger";
$jobs{"mysql"}{"outfile"} = "mysql.sql";
$jobs{"mysql"}{"zipfile"} = "mysql.zip";
# Bloglines blogroll
#$jobs{"bloglines"}{"command"} = "$wget --header=\"Cookie: BloglinesTracker=qzIzPhMKSl4QEg\" http://www.bloglines.com/export";
#$jobs{"bloglines"}{"outfile"} = "Blogroll.xml";
#$jobs{"bloglines"}{"filter"} = "";
# Google Reader blogroll
$jobs{"greader"}{"command"} = "$wget --header=\"Cookie: SID=NnCjXlBvZiifq2Q\" http://www.google.com/reader/subscriptions/export";
$jobs{"greader"}{"outfile"} = "Blogroll.xml";
# del.icio.us bookmarks
$jobs{"del.icio.us"}{"command"} = "$wget --http-user=scott --http-password=tiger --no-check-certificate https://api.del.icio.us/v1/posts/all";
$jobs{"del.icio.us"}{"outfile"} = "del.icio.us.xml";
# this file
$jobs{"mydump"}{"command"} = "cat $ENV{HOME}/perl/mydump.pl";
$jobs{"mydump"}{"outfile"} = "mydump.tmp.pl";
#-------------------- GLOBAL VARIABLE DECLARATION/INITIALIZATION --------------------#
$|=1;
# output path
my ($output_path) = "/path/to/write/files";
# where to email the output
my ($to) = 'you@email.com';
# where email is coming from
my ($from) = 'me ';
# subject line of the email
my ($subject) = "File Backups from " . localtime();
# suffix to append to each job name to get the digest file's name
my ($digest_suffix) = "_digest.txt";
# debugging on/off
my ($debug) = 0;
# override on/off
my ($override) = 0;
# test only mode
my ($test_only) = 0;
# the message that this script will send out
my ($email);
# the body of the message
my ($msg_body) = "";
# attach this if there are errors
my (@errors);
# reusable list to hold some random dump contents
my (@dump_contents);
# files to clean up when done
my (@to_deletes);
#-------------------- COMMAND-LINE PROCESSING --------------------#
getopts("e:doth");
if ($Getopt::Std::opt_h){
print "\n";
print "Usage: mydump.pl [-e ] [-d] | [-o] | [-t] | [-h]\n";
print " -e = override default email address ($to)\n";
print " -d = debug\n";
print " -o = override (send data even if don't have to)\n";
print " -t = test only - don't email results (will override -o if it's set also)\n";
print " -h = get this help message\n\n";
exit(0);
}#if
# override the email address
if ($Getopt::Std::opt_e){
$to = $Getopt::Std::opt_e;
}#if
# Turn debugging on
if ($Getopt::Std::opt_d){
$debug = 1;
msg("Debugging is on.");
}#if
# Turn email override on
if ($Getopt::Std::opt_o){
$override = 1;
msg("Email override is on.");
}#if
# Test only - don't send emails
if ($Getopt::Std::opt_t){
$test_only = 1;
$override = 0;
msg("Testing only - no email will be sent.");
}#if
#-------------------- SUB-ROUTINES --------------------#
# Print debug message
sub msg{
print "[" . localtime() . "]: " . $_[0] . "\n" if $debug;
return 1;
}#sub msg
# initialize the email that will be sent
sub init{
# hate to do this but it makes everything else a little easier
msg("Changing working directory to $output_path...") or die "Could not change to output directory: $!";
chdir $output_path;
# Create a new multipart message
$email = MIME::Lite->new(
From =>$from,
To =>$to,
Subject =>$subject,
Type =>'multipart/mixed'
) or die "Error creating multipart container: $!\n";
}#sub init
# validate that we got what we expected to get
sub validate_contents{
my ($job, $outfile) = @_;
# check that there is a file
my ($filesize) = -s $outfile;
return error($job, "$outfile is empty.") unless $filesize > 0;
# check that file contents match extension given - just for XML files right now
#if( ($outfile =~ m/.*\.xml/) && ($dump_contents[0] !~ m/^\<\?xml/) ){
#return error($job, "XML file $outfile doesn't have ';
close(OLD_DIGEST);
msg("Existing digest for '$job' is: $digest");
return $digest;
}#sub get_old_digest
# get a digest of a dump from the data source
sub get_new_digest{
my ($job) = $_[0];
my ($dump_command) = $jobs{$job}{"command"};
# clear out the contents from the last iteration
@dump_contents = ();
# die if it doesn't exist
open(NEW_DUMP, "$dump_command |") or return "";
my (@unfiltered) = ;
close(NEW_DUMP);
# now see if have to filter out the contents
my ($filter) = $jobs{$job}{"filter"};
if( defined $filter ){
foreach my $line (@unfiltered){
next if $line =~ /$filter/;
push(@dump_contents, $line);
}#foreach
} else {
@dump_contents = @unfiltered;
}#if
my ($digest) = sha1_base64(@dump_contents);
msg("New digest for '$job' is: $digest");
return $digest;
}#sub get_new_digest
# attach the contents from the previous command retrieval
sub attach_contents{
my ($job) = $_[0];
my ($outfile) = $jobs{$job}{"outfile"};
# write the file to the output directory
msg("Writing file $outfile for '$job'...");
open(FILE, ">$outfile") or return error($job, "Could not open $outfile");
print FILE join('', @dump_contents);
close(FILE);
# make sure this will get deleted later
push(@to_deletes, $outfile);
# make sure we got what we were looking for
my ($filesize) = validate_contents($job, $outfile);
return 0 unless $filesize;
# if wants it in zip format, have an extra step
if( defined $jobs{$job}{"zipfile"} ){
my ($zipfile) = $jobs{$job}{"zipfile"};
msg("Zipping up file $zipfile for '$job'...");
system("zip $zipfile $outfile > /dev/null");
push(@to_deletes, $zipfile);
# attach file
msg("Attaching $zipfile...");
$email->attach(Type =>'application/zip',
Path =>$zipfile,
Filename =>$zipfile,
Disposition => 'attachment'
) or return error($job, "Error adding $zipfile: $!");
# bookkeeping
my ($filesize) = -s $zipfile;
$msg_body .= "Includes '$zipfile' from job '$job' ($filesize bytes)\n";
} else {
# attach file
msg("Attaching $outfile...");
$email->attach(Type =>'text/plain',
Path =>$outfile,
Filename =>$outfile,
Disposition => 'attachment'
) or return error($job, "Error adding $outfile: $!");
# bookkeeping
$msg_body .= "Includes '$outfile' from job '$job' ($filesize bytes)\n";
}#if-else
return 1;
}#sub attach_contents
# write the data's digest to a file for checking again next run
sub write_digest{
my ($job, $digest) = @_;
my ($digest_file) = $job.$digest_suffix;
# first write the digest file
msg("Overwriting digest file $digest_file...");
open(FILE, ">$digest_file");
print FILE $digest;
close(FILE);
return 1;
}#sub write_digest
# email the data dump (the digest is not the same)
sub email_files{
msg("Emailing the results to $to...");
return 1 if $test_only;
# add the message body
$email->attach(Type =>'text/plain',
Filename => 'Contents.txt',
Data =>$msg_body
) or die "Error adding the text message part: $!\n";
# if there were errors
if(@errors){
$email->attach(Type =>'text/plain',
Filename => 'Errors.txt',
Data => join('\n', @errors)
) or die "Error adding the text message part: $!\n";
}#if
$email->send;
return 1;
}#sub email_files
# remove the zip file that was created
sub cleanup{
foreach my $file (@to_deletes){
msg("Removing file '$file'...");
unlink($file);
}#foreach
}#cleanup
#------------------ MAIN PROCESSING ------------------#
my ($send_mail) = 0;
init();
#main processing
foreach my $job (keys %jobs){
my ($old_digest) = get_old_digest($job);
my ($new_digest) = get_new_digest($job);
error($job, "Couldn't open data source to get data dump.") if $new_digest eq "";
if( ($new_digest ne $old_digest) || ($override > 0) ){
next unless attach_contents($job);
write_digest($job, $new_digest);
$send_mail += 1;
}#if
}#foreach
email_files() if $send_mail > 0;
cleanup();
msg("Processing completed.");