#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

# VERSION
# PODNAME: systray-mdstat
# ABSTRACT: system tray icon which shows the state of local MD RAIDs

=head1 SYNOPSIS

B<systray-mdstat> [ B<-h> | B<--help> | B<--usage> ]

=head1 DESCRIPTION

This program allows one to display an icon indicating the state of
local Linux Software (MD) RAIDs in any freedesktop.org-compliant
status area.

=head1 SEE ALSO

L<smart-notifier(1)>

=head1 NOTES ABOUT THE ORIGIN OF THE CODE

Parts of the code are very loosely based on the outer framework of
fdpowermon by Wouter Verhelst under Poul-Henning Kamp's "Beer-ware"
license, and the mdstat check from Debian's hobbit-plugins package
written by Christoph Berg under the MIT license. (Both stated that the
amount of code I copied is too small to make their copyright apply,
hence I'm not bound to the licenses they used for their code.)

=cut

use Gtk3 -init;
use Glib::Object::Introspection;
use List::Util qw(max);
use Try::Tiny;
use File::ShareDir ':ALL';
use Pod::Usage;

if (@ARGV and ($ARGV[0] eq '--help'  or
               $ARGV[0] eq '-h')) {
    pod2usage(0);
} elsif (@ARGV and $ARGV[0] eq '--usage') {
    pod2usage(
        -verbose => 2,
        -exitval => 0,
        );
} elsif (@ARGV) {
    pod2usage(
        -message => "E: $0 takes no arguments\n",
        -exitval => 1,
        );
}

my @states = qw( spare ok warn fail );
my @mdstat_checks = (
    # regular expression  => state => string
    [ qr/\[U+\]/          => 1     => 'OK'       ],
    [ qr/(resync|verify)/ => 2     => ' (%s)'    ],
    [ qr/\(F\)/           => 3     => 'FAILED'   ],
    [ qr/\[U*_U*\]/       => 3     => 'DEGRADED' ],
    );

our $use_notify = 1;
my $old_state = 0;
my $icon = Gtk3::StatusIcon->new();
$icon->set_visible(1);

try {
    Glib::Object::Introspection->setup(
        basename => 'Notify',
        version => '0.7',
        package => "Systray::Mdstat::Notify",
        );
    Systray::Mdstat::Notify->init();
} catch {
    say "no notify because setup failed: $@";
    $use_notify = 0;
};

my @icon_dirs = qw(
    share
    /usr/local/share/systray-mdstat
    /usr/share/systray-mdstat
    );

# Unfortunately File::ShareDir functions die instead of returning
# undef if they can't find an according directory.
try {
    push(@icon_dirs, dist_dir('systray-mdstat'));
};


check_mdstat();
Glib::Timeout->add_seconds(3, \&check_mdstat);
Gtk3->main();

sub check_mdstat {
    # 0 = unknown
    # 1 = ok
    # 2 = warning
    # 3 = alarm
    my $state = 0;
    my $text = '';

    my $last_md = '';
    if (-e '/proc/mdstat') {
        open(my $MDSTAT, '<', "/proc/mdstat")
            or die "Can't read from /proc/mdstat despite it exists: $!";
        while (my $line = <$MDSTAT>) {
            if ($line =~ /^(md\d+) *: /) {
                $last_md = $1;
                $text .= "; " unless $text eq '';
            } else {
                foreach my $check (@mdstat_checks) {
                    if ($line =~ $check->[0]) {
                        $state = max($state, $check->[1]);
                        if ($check->[2] =~ /\%/) {
                            $text .= sprintf($check->[2], $&);
                        } else {
                            $text .= "$last_md: ".$check->[2];
                        }
                    }
                }
            }
        }
        close $MDSTAT;
        $text = "/proc/mdstat exists but doesn't contain any RAID."
            unless $text;
    } else {
        $text = "No /proc/mdstat found";
    }

    $icon->set_tooltip_text($text);
    $icon->set_from_file(find_icon_path("harddrive".$states[$state]));

    my $message;
    if ($old_state == 0 and $state != 0) {
        $message = "Current /proc/mdstat state: ".$text;
    } else {
        $message = "/proc/mdstat state changed: ".$text;
    }
    if ($state != $old_state) {
        if ($use_notify) {
            my $notif = Systray::Mdstat::Notify::Notification->new(
                "Software RAID State", $message);
            try {
                $notif->show;
            } catch {
                # Something went wrong trying to show a notification
                # message. Fall back to using a dialog box instead.
                $use_notify = 0;
            };
        }
        if (!$use_notify) {
            my $dialog = Gtk3::MessageDialog->new(
                undef, 'destroy-with-parent', 'warning', 'ok', $message);
            $dialog->run;
            $dialog->destroy;
        }
        $old_state = $state;
    }

    return;
}

sub find_icon_path {
    my $basename = shift;
    my $filename = "$basename.png";
    my $fullpath;
    foreach my $path (@icon_dirs) {
        if (-r "$path/$filename") {
            $fullpath = "$path/$filename";
            last;
        }
    }
    die "Icon $filename not found" unless $fullpath;
    return $fullpath;
}
