#!/usr/bin/perl 
# $Id: digikam_tag_distributor.pl,v 7bdfb2754924 2011/01/26 22:22:17 grin $
#
# (C)2011, Peter Gervai <grin * grin . hu>
# CC-BY-SA-3.0
#
# Distribute Digikam tags to the files with the same unique hash
#
# Usage:
#  Exit digikam, this program likes to have a lock on the db.
#  Run the program, it will scan your db for tags and the images
#   with same unique hash id, and copy all the tags around. 
#  You should not lose any tags, you should end up the sum of
#   all the tags for a given image. 
#  This will "resurrect" "lost" tags when you have moved your 
#   images around (unless someone VACUUM'ed your db).
#  This program _will_not_ find images without hash tags, so please
#   let digikam generate at least a thumb for all.
#  No guarantees whatsoever. If you break your database you keep
#    both parts. (I make a backup for you anyway.)

use DBI;

# 0 means silent.
$DEBUG=1;

if( $DEBUG ) {
	&d(1, "DEBUG level is $DEBUG" );
}

# poor man's backup ;-)
&d(1, "Backing up your database to " .  $ENV{'HOME'} . "/Pictures/digikam4.db.bak" );
system "cp", "-a", $ENV{'HOME'} . "/Pictures/digikam4.db", $ENV{'HOME'} . "/Pictures/digikam4.db.bak";

my $dbname = $ENV{'HOME'} . "/Pictures/digikam4.db";

my $dbh = DBI->connect( "dbi:SQLite:dbname=$dbname", "", "", 
	{ RaiseError => 1, AutoCommit => 0 } );
die "cannot connect db $dbname" unless defined $dbh;
die $dbh->errstr if $dbh->err;

my $cmd; 
my $res;

&d(1, "Start." );

# This inserts the new tag-image relationship
$cmd = "INSERT INTO ImageTags ( imageId, tagID ) VALUES (?,?)";
my $sth_inserttag = $dbh->prepare( $cmd );

# Full list of images / hashes / tags
$cmd = "SELECT id,uniqueHash,tagId FROM Images LEFT JOIN ImageTags ON(imageId=id)";
my $ary = $dbh->selectall_arrayref( $cmd );
&d( 2, $#{$ary} . " total tagged images" );

my (%hash_image, %hash_tag, %image_tag);

# Using perl instead of SQL makes it some 10.000 times faster. :-P
&d(1, "Preprocessing ".$#{ $ary }." image data..." );
for my $i ( 0 .. $#{ $ary } ) {
	
	my ($image,$hash,$tag) = @{$ary->[$i]};
	
	&d(9, "Indexing image $image, hash $hash, tag $tag");
		
	# using hashes makes duplicate detection unnecessary...

	# We cannot really do anything without a hash
	if( $hash eq "" ) {
		&d( 9, "Skip no hash record." );
		next;
	}
	
	# Images for a hash
	$hash_image{ $hash }{ $image }=1;
	
	if( $tag eq "" ) {
		&d( 9, "Skip missing tag record." );
		next;
	}
	
	# tags for a hash
	$hash_tag{ $hash }{ $tag }=1;

	# tags for an image
	$image_tag{ $image }{ $tag } = 1;
}

# Freedom for RAM!
$ary = undef;

# Keep the user awake.
my $image_total = keys %hash_tag;
my $hashcnt = 1;
my $inserts = 0;

&d(1, "Distributing tags to matching hashes (".$image_total." total tagged hashed images).");

# get all the hashes with tags
for my $hash ( keys %hash_tag ) {
	&d( 9, "Processing hash $hash; " . $hashcnt++ . " / $image_total" );
	# walk the tags of a hash
	for my $tagid ( keys %{ $hash_tag{$hash} } ) {		
		&d( 9, "Processing tag $tagid for hash $hash" );
		# walk the images of the hash
		for my $imageid ( keys %{ $hash_image{$hash} } ) {
			&d( 9, "Checking image $imageid for tag $tagid" );
			# if the image doesn't have the tag, do tag it then
			if( !defined( $image_tag{ $imageid }{ $tagid } ) ) {
				&d( 2, "Inserting tag $tagid for image $imageid" );
				$res = $sth_inserttag->execute( $imageid, $tagid );
				$inserts++;
				if( $res != 1 ) {
					# Well, RaiseError should not let us to be talkative
					&d( 1, "Error inserting (id,tag) (" . $imageid .",". $tagid );
				}
			}
		}
	}
}

&d( 1, "Committing changes to database.");
$dbh->commit;	

&d( 1, "I've made $inserts changes. I hope it helped. Have a nice day! ;-)" );
exit 0;


# Poor man's debug
sub d ($$) {
	my ($lvl, $str) = @_;
	return if $lvl > $DEBUG;
	print STDERR scalar(localtime), "[$$] {$lvl} $str\n";
}
