#!/usr/bin/env perl ## Copyright © 2010 by Daniel Friesel ## License: WTFPL ## 0. You just DO WHAT THE FUCK YOU WANT TO. use strict; use warnings; use 5.010; use autodie; use Cwd; use Digest::SHA qw(sha1_hex); use File::Find; use Getopt::Long; use Storable qw(nstore retrieve); use Time::Progress; my $base = getcwd(); my $rel_paths = 1; my $read_size = (2 ** 20) * 4; # 4 MiB my $db_file = "$base/.hashl.db"; my $total = 0; my $cur = 0; my $timer; my $db; GetOptions( 'd|database=s' => \$db_file, ); my $action = $ARGV[0]; if (not defined $action) { die("Usage: $0 \n"); } if (-r $db_file) { $db = retrieve($db_file); } sub get_total { my $file = $File::Find::name; if (-f $file and $file ne $db_file) { $total++; } } sub drop_deleted { for my $file (keys %{$db}) { if (! -e $file) { delete $db->{$file}; } } } sub hash_file { my ($file) = @_; my ($fh, $data); open($fh, '<', $file); binmode($fh); read($fh, $data, $read_size); close($fh); return sha1_hex($data); } sub is_in_list { my ($file) = @_; my $hash = hash_file($file); if (grep { $_->{'hash'} eq $hash } values %{$db}) { return 1; } return 0; } sub process_file { my $file = $File::Find::name; my $path = $file; my ($size, $mtime) = (stat($file))[7,9]; local $| = 1; if (not -f $file or $file eq $db_file) { return; } $cur++; if ($rel_paths) { $file = substr($file, length($base) + 1); } print $timer->report("\r\e[2KUpdating: %p done, %L elapsed, %E remaining", $cur); if (exists($db->{$file}) and $db->{$file}->{'mtime'} == $mtime and $db->{$file}->{'size'} == $size ) { return; } $db->{$file} = { hash => hash_file($path), mtime => $mtime, size => $size, }; if (($cur % 100) == 0) { nstore($db, $db_file); } } if ($action eq 'update') { drop_deleted(); find(\&get_total, $base); $timer = Time::Progress->new(); $timer->attr( min => 1, max => $total ); find(\&process_file, $base); print "\n"; nstore($db, $db_file); } elsif ($action eq 'list') { for my $name (sort keys %{$db}) { my $file = $db->{$name}; printf("%s %s\n", $file->{'hash'}, $name); } } elsif ($action eq 'in-list') { if ($ARGV[1]) { exit (!is_in_list($ARGV[1])); } else { while (my $line = ) { chomp $line; if (!is_in_list($line)) { say $line; } } } } __END__ =head1 NAME =head1 SYNOPSIS =head1 DESCRIPTION =head1 OPTIONS =head1 EXIT STATUS =head1 CONFIGURATION =head1 DEPENDENCIES =head1 BUGS AND LIMITATIONS =head1 AUTHOR Copyright (C) 2010 by Daniel Friesel Ederf@finalrewind.orgE =head1 LICENSE 0. You just DO WHAT THE FUCK YOU WANT TO.