The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mojolicious::Plugin::StaticCompressor::File;
# Single-file cache container

use strict;
use warnings;
use utf8;

use Encode;
use JavaScript::Minifier qw//;
use CSS::Minifier qw//;
use Mojo::Asset::File;
use Mojo::Util qw//;

sub new {
	my ($class, %hash) = @_;
	my $s = bless({}, $class);

	# Initialize 
	$s->{mojo_static} = $hash{config}->{mojo_static} || die('Not specified mojo_static');
	$s->{path_cache_dir} = $hash{config}->{path_cache_dir} || die('Not specified path_cache_dir');
	$s->{path_single_cache_dir} = $hash{config}->{path_single_cache_dir} || die('Not specified path_single_cache_dir');

	$s->{file_key} = undef;
	$s->{path_file} = $hash{path_file} || die('Not specified path_file'); # Path of raw file
	$s->{extension} = $hash{extension} || die('Not specified extension');
	$s->{is_minify} = $hash{is_minify} || 0;

	$s->{is_use_cache} = ($s->{is_minify}) ? 1 : 0;
	$s->{updated_at} = undef; # Updated_at of processed (epoch sec)
	$s->{content} = undef; # processed content
	$s->{path_cached_file} = undef;

	if(defined $s->{path_file} && $s->{is_use_cache}){
		$s->_load_file();
	}

	return $s;
}

# Generate the key of file
sub get_key {
	my $s = shift;
	if($s->{is_use_cache}){
		if(!defined $s->{file_key}){
			$s->{file_key} = Mojo::Util::sha1_sum( $s->{path_file} ).'.'.$s->{extension};
		}
		return $s->{file_key};
	} else {
		return;
	}
}

# Get the processed content
sub get_content {
	my $s = shift;
	# Check for update of source file
	$s->_load_file();
	return $s->{content};
}

# Get for updated_at
sub get_updated_at {
	my $s = shift;
	# Check for update of source file
	$s->_load_file();
	return $s->{updated_at};
}

# Get the path of file (raw)
sub get_raw_path {
	my $s = shift;
	return $s->{path_file};
}

# Processing the file
sub _process {
	my $s = shift;
	$s->_load_file();
	if($s->{is_minify}){
		$s->_minify();
	}
}

# Check and make the single cache directory
sub _check_cache_dir {
	my $s = shift;
	if(! -d $s->{path_single_cache_dir}){
		# Make the directory
		mkdir( $s->{path_single_cache_dir} ) || die("Can't make a directory: ".$s->{path_single_cache_dir});
	}
}

# Load content from the file and process
sub _load_file {
	my $s = shift;
	if(! $s->{is_use_cache}){
		my $asset;
		eval {
			$asset = $s->{mojo_static}->file($s->{path_file});
			my $updated_at = (stat($asset->path()))[9];
			if(!defined $s->{updated_at} || $s->{updated_at} < $updated_at){ # Is Updated
				$s->{content} = Encode::decode_utf8($asset->slurp());
				$s->{updated_at} = $updated_at;
			}
		}; if($@){ die ("Can't read static file: ". $s->{path_file} ."\n$@"); }
		return;
	}

	$s->_check_cache_dir();

	# Generate cache path
	$s->{path_cached_file} = $s->{path_single_cache_dir}.$s->get_key();

	# Load the file from cache
	if(defined $s->{path_cached_file}){
		eval{
			my $cache = Mojo::Asset::File->new( path => $s->{path_cached_file} );
			$s->{updated_at} = (stat($s->{path_cached_file}))[9];
			$s->{content} = $cache->slurp();
		};
	}

	# Load the file and check for update
	my ($asset, $updated_at, $raw_content);
	eval {
		$asset = $s->{mojo_static}->file($s->{path_file});
		$updated_at = (stat($asset->path()))[9];
		$raw_content = Encode::decode_utf8($asset->slurp());
	}; if($@){ die ("Can't read static file: ".$asset->path()."\n$@"); }

	# Process and cache
	if(!defined $s->{updated_at} || $s->{updated_at} < $updated_at){ # Is Updated
		$s->{content} = $raw_content;
		$s->{updated_at} = $updated_at;
		
		if($s->{is_minify}){
			# Process the file
			$s->_minify();
			# Cache to the file
			my $cache = Mojo::Asset::File->new();
			$cache->add_chunk( Encode::encode_utf8($s->{content}) );
			$cache->move_to( $s->{path_cached_file} );
		}
	}
}

# Minify the content
sub _minify {
	my $s = shift;

	if($s->{extension} eq 'js'){
		$s->{content} = JavaScript::Minifier::minify(input => $s->{content});
	} elsif($s->{extension} eq 'css'){
		$s->{content} = CSS::Minifier::minify(input => $s->{content});
	} else {
		die('Not supported file type');
	}
}

1;
__END__
=head1 NAME

Mojolicious::Plugin::StaticCompressor::File

=head1 SYNOPSIS

This is internal package that manipulate for single file.

Please see POD for L<Mojolicious::Plugin::StaticCompressor>.

L<https://github.com/mugifly/p5-Mojolicious-Plugin-StaticCompressor/blob/master/README.pod>

=head1 METHODS

=head2 new ( ... )

Initialize a instance for single file.

=head2 get_key ( )

Get a cache key of the file. (If necessary, generate it.)

=head2 get_content ( )

Get the processed content of the file.
(Check for updates of source files. And if necessary, update cache.)

=head2 get_updated_at ( )

Get te updated_at (epoch seconds) from of the file.
(Check for updates of source files. And if necessary, update cache.)

=head2 get_raw_path ( )

Get a path of the source file.

=head1 SEE ALSO

L<Mojolicious::Plugin::StaticCompressor> ( L<https://github.com/mugifly/p5-Mojolicious-Plugin-StaticCompressor> )

=head1 COPYRIGHT AND LICENSE

Please see POD for L<Mojolicious::Plugin::StaticCompressor>.

L<https://github.com/mugifly/p5-Mojolicious-Plugin-StaticCompressor/blob/master/README.pod>