#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw(floor);
use List::Util qw(sum);

########################################## main #############################################

my ($users_info, $domains_info) = parseConfig("STDIN");

my $ips_usernames = mapIPsToUsernames();

# load disk space usages
loadDiskUsage($users_info, $domains_info);
loadMysqlDiskUsage($users_info, $domains_info);
loadPgsqlDiskUsage($users_info, $domains_info);

# load traffic usages
loadWebTraffic($domains_info);
loadFtpTraffic($users_info, $ips_usernames);

detectFakeDomains($users_info, $domains_info);
reportUsages($users_info, $domains_info);

updateLastReportDate();

########################################## subs #############################################

# these files must be created during migration, to comply with the dates of traffic usage transferred to Plesk
use constant PLESK_FTP_MTIME_PATH => "/hsphere/local/var/statistic/Plesk/ftp.mtime";
use constant PLESK_ANONFTP_MTIME_PATH => "/hsphere/local/var/statistic/Plesk/anonftp.mtime";
use constant PLESK_WEB_MTIME_PATH => "/hsphere/local/var/statistic/Plesk/web.mtime";

sub updateLastReportDate {
	system("touch " . PLESK_FTP_MTIME_PATH . " " . PLESK_ANONFTP_MTIME_PATH . " " . PLESK_WEB_MTIME_PATH);
}

# parse config fed by Plesk and get only necessary domain properties from it
sub parseConfig {
	my ($handle) = @_;
	my (%users_info, %domains_info);
	while (<$handle>) {
		chomp;
		my @some_props = split(/:/);
		if (scalar(@some_props) < 6) {
			next;	# not domain properties
		}
		my $domain_name = $some_props[0];
		my $domain_id = $some_props[1];
		my $user_name = $some_props[5];
		if (!exists $users_info{$user_name}) {
			$users_info{$user_name} = {
			    'logs_disk_usage' => 0,
			    'anonftp_disk_usage' => 0,
			    'db_disk_usage' => 0,
			    'other_disk_usage' => 0
			}
		}
		push @{$users_info{$user_name}{'domains'}}, $domain_name;
		$domains_info{$domain_name} = {
		    'user_name' => $user_name,
		    'domain_id' => $domain_id,
		    'disk_usage' => 0
		};
	}

	return \%users_info, \%domains_info;
}

sub detectFakeDomains {
	my ($users_info, $domains_info) = @_;

	while (my ($user_name, $user_info) = each %$users_info) {
		# determine which of the specified domains is fake (system) one
		# TBD if to change Plesk php code and specify explicitly when a domain is fake
		for my $domain_name (@{$user_info->{'domains'}}) {
			if (defined $domains_info->{$domain_name}{'disk_usage'}) {
				my $domain_info = $domains_info->{$domain_name};
				$domain_info->{'is_fake'} = 1;
				$user_info->{'fake_domain_id'} = $domain_info->{'domain_id'};
				$user_info->{'fake_domain_name'} = $domain_name;
				last;
			}
		} # for $domain_name
	} # for each user
}

#################### General disk usage

sub loadDiskUsage {
	my ($users_info, $domains_info) = @_;

	while (my ($user_name, $user_info) = each %$users_info) {
		open(my $duHandle, "cd ~$user_name && du -b --max-depth=1 . |");
	
		# disk used by recognizable objects (domains, anonftp, logs) shall be assigned to them
		my ($total_usage, $domains_usage, $logs_usage, $anonftp_usage) = (0, 0, 0, 0);
		while (<$duHandle>) {
			chomp;
			# data example:
			# 4116    ./stats
			# 2173205 ./chuck.geck
			# 10734   ./logs
			# 15219   ./1111
			# 3421115 .
			if (/^(\d+)\s+\.\/(.+)$/) {
				my ($usage, $domain_name) = ($1, $2);
				if (exists $domains_info->{$domain_name}) {
					$domains_usage += $usage;
					$domains_info->{$domain_name}{'disk_usage'} = $usage;
				} elsif ($domain_name eq "logs") {
					$logs_usage += $usage;
				} elsif ($domain_name =~ /^\d+$/) {
					$anonftp_usage += $usage;
				}
			} elsif (/^(\d+)\s+\.$/) {
				$total_usage = $1;
			}
		} # while <$duHandle>
		close($duHandle);

		$user_info->{'logs_disk_usage'} = $logs_usage;
		$user_info->{'anonftp_disk_usage'} = $anonftp_usage;
		# remaining disk usage shall be assigned to user
		$user_info->{'other_disk_usage'} = $total_usage - $domains_usage - $logs_usage - $anonftp_usage;
	} # for each user
}

#################### DB disk usage

sub loadMysqlDiskUsage {
	my ($users_info, $domains_info) = @_;

	loadDbDiskUsage($users_info, $domains_info,
	    "/hsphere/local/var/statistic/mysql_usage.txt", 'information_schema');
}

sub loadPgsqlDiskUsage {
	my ($users_info, $domains_info) = @_;

	loadDbDiskUsage($users_info, $domains_info,
	    "/hsphere/local/var/statistic/pgsql_usage.txt");
}

sub loadDbDiskUsage {
	my ($users_info, $domains_info, $file_name, @excluded_db_names) = @_;

	open(my $db_stats, $file_name) or return;
	while (<$db_stats>) {
		chomp;
		my ($db_name, $size_mb) = split(/\|/);

		next if (grep(/^$db_name$/, @excluded_db_names));
		# data example:
		# chuck_dbase
		# template1
		if ($db_name =~ /^([^_]+)_(.+)$/) {
			my $user_name = $1;
			if (exists $users_info->{$user_name}) {
				my $user_info = $users_info->{$user_name};
				$user_info->{'db_disk_usage'} += $size_mb;
			} # if user requested by Plesk
		} # if DB name in H-Sphere's format <username>_<suffix>
	} # for each line of both stats files
	close($db_stats);
}

#################### Traffic usage

# TBD it is more safe to use already rotated ones ONLY - they can't be rotated again in midst
# at the other hand this way Plesk will not have today's traffic usages..

sub loadFtpTraffic {
	my ($users_info, $ips_usernames) = @_;

	# .gst file contains authenticated transfers stats, .ftp contains anonymous transfers stats
	my $stat_files_list_cmd = "find /hsphere/local/var/statistic " .
	    "-name \"??.??.????.ftp.txt*\" -and -newer " . PLESK_ANONFTP_MTIME_PATH .
	    " -or " .
	    "-name \"??.??.????.gst.txt*\" -and -newer " . PLESK_FTP_MTIME_PATH;

	loadTraffic($stat_files_list_cmd, $users_info, $ips_usernames, \&processFtpTrafficRecord);
}

sub loadWebTraffic {
	my ($domains_info) = @_;

	my $stat_files_list_cmd = "find /hsphere/local/var/statistic " .
	    "-name \"??.??.????.txt*\" -and -newer " . PLESK_WEB_MTIME_PATH;

	loadTraffic($stat_files_list_cmd, $domains_info, undef, \&processWebTrafficRecord);
}

sub loadTraffic {
	my ($stat_files_list_cmd, $assignees, $ips_usernames, $processRecordFunction) = @_;

	open(my $filenames_fd, "$stat_files_list_cmd |") or return;

	push my @filenames, <$filenames_fd>;
	close($filenames_fd);

	for my $filename (@filenames) {
		chomp $filename;
		# data example:
		# /hsphere/local/var/statistic/27.01.2012.txt
		# /hsphere/local/var/statistic/25.01.2012.gst.txt
		# /hsphere/local/var/statistic/loaded/26.01.2012.txt.gz
		# /hsphere/local/var/statistic/25.01.2012.ftp.txt
		if ($filename =~ /(\d\d)\.(\d\d)\.(\d\d\d\d)\.(ftp\.|gst\.|)txt(\.gz)?$/) {
			my $date = "$3-$2-$1";
			my $compressed = $5;

			open(my $stats, ($compressed ? "gzip -dc $filename |" : $filename)) or next;
			while (<$stats>) {
				chomp;
				$processRecordFunction->($assignees, $ips_usernames, $date, $_);
			}
			close($stats);
		}
	}
}

sub processFtpTrafficRecord {
	my ($users_info, $ips_usernames, $date, $record) = @_;

	my $username;
	my ($user, $kbytes, $files) = split(/\|/, $record);
	if (defined $user and exists $ips_usernames->{$user}) {
		$username = $ips_usernames->{$user};
	} else {
		$username = $user;
	}

	if (defined $username and exists $users_info->{$username}) {
		# ftp_out is a classifier expected by Plesk
		$users_info->{$username}{'traffic'}{$date}{'ftp_out'} += $kbytes;
	}
}

sub processWebTrafficRecord {
	my ($domains_info, $ips_usernames, $date, $record) = @_;

	my ($domain_name, $in_kbytes, $out_kbytes) = split(/\|/, $record);
	# http_in and http_out are the classifiers expected by Plesk
	if (defined $domain_name and exists $domains_info->{$domain_name}) {
		$domains_info->{$domain_name}{'traffic'}{$date}{'http_in'} += $in_kbytes;
		$domains_info->{$domain_name}{'traffic'}{$date}{'http_out'} += $out_kbytes;
	}
}

sub mapIPsToUsernames {
	my %ips_usernames;

	for my $filename (glob("/hsphere/local/config/ftpd/sites/*.conf")) {
		open(my $config, $filename) or next;
		my ($ip, $username);
		while (<$config>) {
			chomp;
			# data example:
			# <VirtualHost 10.50.51.194>
			#        User chuck
			#	 Group chuck
			# ...
			if (/^<VirtualHost (.+)>$/) {
				$ip = $1;
			} elsif (/^\s*User\s+([^\s]+)$/) {
				$username = $1;
			}
			if ($ip && $username) {
				$ips_usernames{$ip} = $username;	
				last;
			}
		} # while <$config>
		close($config);
	} # for each FTP site config

	return \%ips_usernames;
}

#################### Usage reporting

sub reportUsages {
	my ($users_info, $domains_info) = @_;
	while (my ($user_name, $user_info) = each %$users_info) {
		my $fake_domain_name = $user_info->{'fake_domain_name'};
		my $fake_domain_id = $user_info->{'fake_domain_id'};

		for my $domain_name (@{$user_info->{'domains'}}) {
			my $domain_info = $domains_info->{$domain_name};
			printTrafficUsage($fake_domain_id, $domain_info->{'traffic'})
			    unless (exists $domain_info->{'is_fake'});
		}
		printTrafficUsage($fake_domain_id, $user_info->{'traffic'});

			
		printDiskSpaceUsage($fake_domain_id, $fake_domain_name,
		    {
		    'httpdocs' => calculateWebDiskUsage($user_info->{'domains'}, $domains_info),
		    'dbases' => floor($user_info->{"db_disk_usage"} * 1024 * 1024),
		    'logs' => $user_info->{"logs_disk_usage"},
		    "anonftp" => $user_info->{"anonftp_disk_usage"},
		    "chroot" => $user_info->{"other_disk_usage"}
		    }
		);
	} # for each user requested by Plesk
}

sub calculateWebDiskUsage {
	my ($domains, $domains_info) = @_;
	
	my $web_disk_usage = 0;
	for my $domain_name (@$domains) {
		my $domain_info = $domains_info->{$domain_name};
		$web_disk_usage += $domain_info->{'disk_usage'}
		    unless (exists $domain_info->{'is_fake'});
	}

	return $web_disk_usage;
}

sub printTrafficUsage {
	my ($domain_id, $traffic_info) = @_;

	while (my ($date, $traffics) = each %$traffic_info) {
		next unless (%$traffics);
		print "type = domaintraffic, " .
		    "dom_id = " . $domain_id . ", " .
		    "date = " . $date;
		while (my ($traffic_type, $kbytes) = each %$traffics) {
			print ", $traffic_type = " . floor($kbytes * 1024);
		}
		print "\n";
	}
}

sub printDiskSpaceUsage {
	my ($domain_id, $domain_name, $usages) = @_;

	my $line = "type = diskspace, dom_id = $domain_id, dom_name = $domain_name";
	my $summary_usage = sum values %$usages;
	if ($summary_usage == 0) {
		return;
	}

	while (my ($usage_type, $usage_value) = each %$usages) {
		if ($usage_value != 0) {
			$line .= ", $usage_type = $usage_value";
		}
	}
	$line .= ", real_size = $summary_usage\n";

	print $line;
}
