#!/usr/bin/perl # program: iCalDoubleRemover.perl # author: Patrick.Stein@jinx.de # # purpose: remove duplicate entries from iCal's and give them a new unique id # # usage: just call it and it will figure out the same entries in one calendar # # # a ical File looks like # # HEADER # VEVENTS # FOOTER # Vevents look like # BEGIN:VEVENT # DTSTART;TZID=Europe/Berlin:20020912T180000 # DTEND;TZID=Europe/Berlin:20020912T211500 # SUMMARY:Snookern # UID:ECB5EBB6-A038-11D8-A92C-000A95A733C0 # SEQUENCE:3 # DTSTAMP:20040408T084610Z # END:VEVENT use strict; use Digest::MD5 qw( md5_hex ); ### # read in the commandline /config file options ### use Getopt::Long; use Config::IniFiles; my %commandlineoption; { my %default = ( 'configurationfilename' => ['config.ini','string'], 'createbackup' => [1,'flag'], 'backupextension' => ['icaldoubleremoverbackup','string'], 'icaldirectory' => [$ENV{HOME}.'/Library/Calendars','string'], 'help' => [0,'option'], 'debug' => [0,'flag'], ); my %optionconverter = ('string' => '=s', 'number' =>,'=i', 'flag'=>'','option'=>'', ); my @ARGVCOPY = @ARGV; GetOptions( \%commandlineoption, map($_.$optionconverter{${$default{$_}}[-1]},keys %default) ); @ARGV = @ARGVCOPY; my $configfilename = (defined($commandlineoption{'configurationfilename'})?$commandlineoption{'configurationfilename'}:${$default{'configurationfilename'}}[0]); my $configurationObject = new Config::IniFiles( -file => $configfilename ) if -e $configfilename; my $packagename = __PACKAGE__; $packagename = $0 if __PACKAGE__ eq 'main'; %commandlineoption = map { $_ => (defined($commandlineoption{$_})?$commandlineoption{$_} :(($configurationObject && defined($configurationObject->val($packagename,$_)))?$configurationObject->val($packagename,$_) :(($configurationObject && defined($configurationObject->val('GLOBAL',$_)))?$configurationObject->val('GLOBAL',$_):${$default{$_}}[0])) ) } keys(%default); if( $commandlineoption{help} ) { warn "[$packagename] options are :\n",join("\t\n",map(sprintf("--%-30s default: %s%s",$_.' ('.${$default{$_}}[-1].')',${$default{$_}}[0],($commandlineoption{$_} ne ${$default{$_}}[0]?sprintf("\n%-32s current: %s",'',$commandlineoption{$_}):'')),sort keys %default))."\n"; exit if __PACKAGE__ eq 'main' ; } } #### # main program starts here # # find out all calendar files and work through them # # print STDERR "Finding calendar files in $commandlineoption{icaldirectory}\n"; if(open(FINDPIPE,'find '.$commandlineoption{icaldirectory}.' -type f -name *.ics |') ) { WORKONFILES: while( my $filename= ) { chomp($filename); print STDERR "Working on file $filename\n"; workoncalenderfile($filename,($commandlineoption{createbackup}?$commandlineoption{backupextension}:undef)); } close(FINDPIPE); } else { die "Couldn't open find command due to $!"; } print "Work done."; exit; sub workoncalenderfile { my($originalfilename,$backupextension)=@_; my $intermediatefilename = $originalfilename.'.icaldoubleremoverintermediatefile'; if( ! open(ORIGINALCALENDARFILE,'<'.$originalfilename) ) { die "Can't open Calender $originalfilename due to $!\n"; } if( ! open(NEWCALENDARFILE,'>'.$intermediatefilename) ) { die "Can't open intemediate Calender $intermediatefilename due to $!\n"; } my $state = 0; my @currentevent; my %knownevents; while() { chop; if( 0 == $state ) { if( /^BEGIN:VEVENT\s*$/ ) { $state = 1; @currentevent=($_); } elsif( /^END:VEVENT\s*$/ ) { die "Found end of VEVENT even though not inside event in line $."; } else { print NEWCALENDARFILE $_."\n"; # do nothing } } elsif( 1 == $state ) { if( /^BEGIN:VEVENT\s*$/ ) { die "Found BEGIN of VEVENT even though inside event in line $."; } elsif( /^END:VEVENT\s*$/ ) { push(@currentevent,$_); $state = 0; my $currentcardstrippedstring; my $currentcardstrippedmd5; foreach my $line (sort @currentevent) { $currentcardstrippedstring.=$line if $line !~ /^(UID|DTSTAMP|DESCRIPTION\:AddressBook\s*UID):/; } $currentcardstrippedmd5 = md5_hex($currentcardstrippedstring); if( exists( $knownevents{$currentcardstrippedmd5} ) ) { # event already known; print STDERR "Event already known - wont print:\n"; print STDERR "Currentevent:\n".join("\n",@currentevent)."\n"; print STDERR "Knownevent:\n".join("\n",@{$knownevents{$currentcardstrippedmd5}})."\n"; } else { $knownevents{$currentcardstrippedmd5} = \@currentevent; print NEWCALENDARFILE join("\n",@currentevent)."\n"; } } else { push(@currentevent,$_); } } } close(ORIGINALCALENDARFILE); close(NEWCALENDARFILE); if( $backupextension ) { die "Can't create backup file $originalfilename.$backupextension due to $!" if !rename($originalfilename,$originalfilename.'.'.$backupextension); } die "Can't move intermediate ($intermediatefilename) to original ($originalfilename) file due to $!" if !rename($intermediatefilename,$originalfilename); }