This commit is contained in:
Nick Bebout 2011-03-12 02:44:50 +00:00
parent f864a2cb10
commit 7299eabfd8
6 changed files with 299 additions and 267 deletions

31
CREDITS
View file

@ -1,5 +1,5 @@
#!/bin/cat
# $Id: CREDITS,v 1.127 2009/12/09 00:12:24 gilles Exp gilles $
# $Id: CREDITS,v 1.128 2010/01/16 01:20:06 gilles Exp gilles $
If you want to make a donation to the author, Gilles LAMIRAL:
@ -19,6 +19,26 @@ to remove one.
I thank very much all of these people.
Robert Bauer
Contributed by giving the book
31.50 "Programming Interactivity: A Designer's Guide to Processing, Arduino, and Openframeworks" by Joshua Noble
R.W. Rodolico
Contributed by giving the book
25.60 "Le Ton Beau De Marot: In Praise Of The Music Of Language" by Clément Marot, Douglas R. Hofstadter
Martin & Simon Clauss
Contributed by giving the book
23.09 "The Best of Instructables Volume I: Do-It-Yourself Projects from the World's Biggest Show & Tell (v. 1)" by MAKE magazine and Instructables.com
Phil Haigh
Contributed by giving the book
29.70 "The Failure of Risk Management: Why It's Broken and How to Fix It" by Douglas W. Hubbard
Frank Justin Woodman
Contributed by giving the book
31.49 "JavaScript: The Definitive Guide" by David Flanagan
Matt Garretson
Contributed by giving the books
42.66 "Beautiful Testing: Leading Professionals Reveal How They Improve Software (Theory in Practice)" by Adam Goucher
@ -756,7 +776,12 @@ Eric Yung
Total amount of book prices :
c \
31.50+\
25.60+\
23.09+\
29.70+\
31.49+\
\
9.50+\
\
42.66+\
@ -841,4 +866,4 @@ c \
31.20+\
40.00
=
1985.27
2136.15

View file

@ -1,17 +1,24 @@
RCS file: RCS/imapsync,v
Working file: imapsync
head: 1.299
head: 1.300
branch:
locks: strict
gilles: 1.299
gilles: 1.300
access list:
symbolic names:
keyword substitution: kv
total revisions: 299; selected revisions: 299
total revisions: 300; selected revisions: 300
description:
----------------------------
revision 1.299 locked by: gilles;
revision 1.300 locked by: gilles;
date: 2010/01/16 03:34:37; author: gilles; state: Exp; lines: +250 -250
Changed name of variables. "from" replaced by imap1 "to" by imap2.
f_* replaced by h1_*
t_* replaced by h2_*
Cosmetic but easier to read nd maintain.
----------------------------
revision 1.299
date: 2010/01/15 00:19:32; author: gilles; state: Exp; lines: +10 -10
imapsync is no longer GPL software.
imapsync is WTFPL software. The best licence I found.

8
README
View file

@ -3,7 +3,7 @@ NAME
Synchronise mailboxes between two imap servers. Good at IMAP migration.
More than 32 different IMAP server softwares supported with success.
$Revision: 1.299 $
$Revision: 1.300 $
INSTALL
imapsync works fine under any Unix OS with perl.
@ -301,8 +301,8 @@ IMAP SERVERS
this section report the two lines at the begining of the output if they
are useful to know the softwares. Example:
From software:* OK louloutte Cyrus IMAP4 v1.5.19 server ready
To software:* OK Courier-IMAP ready
Host1 software:* OK louloutte Cyrus IMAP4 v1.5.19 server ready
Host2 software:* OK Courier-IMAP ready
You can use option --justconnect to get those lines. Example:
@ -362,5 +362,5 @@ SIMILAR SOFTWARES
Feedback (good or bad) will always be welcome.
$Id: imapsync,v 1.299 2010/01/15 00:19:32 gilles Exp gilles $
$Id: imapsync,v 1.300 2010/01/16 03:34:37 gilles Exp gilles $

12
TODO
View file

@ -1,9 +1,13 @@
#!/bin/cat
# $Id: TODO,v 1.69 2010/01/14 23:41:43 gilles Exp gilles $
# $Id: TODO,v 1.70 2010/01/16 03:38:47 gilles Exp gilles $
TODO file for imapsync
----------------------
Post on newsgroup comp.mail.imap when a new release comes.
http://groups.google.fr/group/comp.mail.imap
Post on imapsync mailing-list when a new release comes.
Add option --exclude_messages_with_flag
@ -17,6 +21,7 @@ body. Have to code this.
Add an option to store flags with "FLAGS.SILENT" instead of "+FLAGS.SILENT".
No, be "FLAGS.SILENT" the default and "+FLAGS.SILENT" an option.
Add TLS support with patches/imapsync-1.217_tls_support.patch
@ -29,8 +34,6 @@ Take a look at Mail::IMAPTalk
Simon Bertrang said "way better performance, less problems, easier to use and no
issues so far". Sounds good!
Post on imapsync mailing-list when a new release comes.
Add an option to implement the faq entry about copying a contact folder.
imapsync doesn't report well. It should says "I had
@ -118,9 +121,6 @@ Add a method doing the switch automagicaly.
Add --verbose from Kjetil jumbo patch.
Post on newsgroup comp.mail.imap when a new release comes.
http://groups.google.fr/group/comp.mail.imap
Read the IMAP RFC http://www.faqs.org/rfcs/rfc3501.html
Add debian packaging in the Makefile.

View file

@ -1 +1 @@
1.299
1.300

498
imapsync
View file

@ -9,7 +9,7 @@ tool. Synchronise mailboxes between two imap servers. Good
at IMAP migration. More than 32 different IMAP server softwares
supported with success.
$Revision: 1.299 $
$Revision: 1.300 $
=head1 INSTALL
@ -336,8 +336,8 @@ future users. To help the author maintaining this section
report the two lines at the begining of the output if they
are useful to know the softwares. Example:
From software:* OK louloutte Cyrus IMAP4 v1.5.19 server ready
To software:* OK Courier-IMAP ready
Host1 software:* OK louloutte Cyrus IMAP4 v1.5.19 server ready
Host2 software:* OK Courier-IMAP ready
You can use option --justconnect to get those lines.
Example:
@ -418,7 +418,7 @@ Entries for imapsync:
Feedback (good or bad) will always be welcome.
$Id: imapsync,v 1.299 2010/01/15 00:19:32 gilles Exp gilles $
$Id: imapsync,v 1.300 2010/01/16 03:34:37 gilles Exp gilles $
=cut
@ -487,7 +487,7 @@ my(
use vars qw ($opt_G); # missing code for this will be option.
$rcs = '$Id: imapsync,v 1.299 2010/01/15 00:19:32 gilles Exp gilles $ ';
$rcs = '$Id: imapsync,v 1.300 2010/01/16 03:34:37 gilles Exp gilles $ ';
$rcs =~ m/,v (\d+\.\d+)/;
$VERSION = ($1) ? $1: "UNKNOWN";
@ -551,8 +551,8 @@ while (@argv_copy) {
my $banner = join("",
'$RCSfile: imapsync,v $ ',
'$Revision: 1.299 $ ',
'$Date: 2010/01/15 00:19:32 $ ',
'$Revision: 1.300 $ ',
'$Date: 2010/01/16 03:34:37 $ ',
"\n",localhost_info(),
" and the module Mail::IMAPClient version used here is ",
$VERSION_IMAPClient,"\n",
@ -643,17 +643,17 @@ sub localhost_info {
}
if ($justconnect) {
my $from = ();
my $to = ();
my $imap1 = ();
my $imap2 = ();
$from = connect_imap($host1, $port1, $debugimap, $ssl1);
print "From software: ", server_banner($from);
print "From capability: ", join(" ", $from->capability()), "\n";
$to = connect_imap($host2, $port2, $debugimap, $ssl2);
print "To software: ", server_banner($to);
print "To capability: ", join(" ", $to->capability()), "\n";
$from->logout();
$to->logout();
$imap1 = connect_imap($host1, $port1, $debugimap, $ssl1);
print "Host1 software: ", server_banner($imap1);
print "Host1 capability: ", join(" ", $imap1->capability()), "\n";
$imap2 = connect_imap($host2, $port2, $debugimap, $ssl2);
print "Host2 software: ", server_banner($imap2);
print "Host2 capability: ", join(" ", $imap2->capability()), "\n";
$imap1->logout();
$imap2->logout();
exit(0);
}
@ -718,8 +718,8 @@ $fastio2 = (defined($fastio2)) ? $fastio2 : 0;
@useheader = ("ALL") unless (@useheader);
print "From imap server [$host1] port [$port1] user [$user1]\n";
print "To imap server [$host2] port [$port2] user [$user2]\n";
print "Host1 imap server [$host1] port [$port1] user [$user1]\n";
print "Host2 imap server [$host2] port [$port2] user [$user2]\n";
sub ask_for_password {
@ -746,26 +746,26 @@ $password2 || $passfile2 || do {
$password2 = (defined($passfile2)) ? firstline ($passfile2) : $password2;
my $from = ();
my $to = ();
my $imap1 = ();
my $imap2 = ();
$timestart = time();
$timebefore = $timestart;
$debugimap and print "From connection\n";
$from = login_imap($host1, $port1, $user1, $password1,
$debugimap and print "Host1 connection\n";
$imap1 = login_imap($host1, $port1, $user1, $password1,
$debugimap, $timeout, $fastio1, $ssl1,
$authmech1, $authuser1, $reconnectretry1);
$debugimap and print "To connection\n";
$to = login_imap($host2, $port2, $user2, $password2,
$debugimap and print "Host2 connection\n";
$imap2 = login_imap($host2, $port2, $user2, $password2,
$debugimap, $timeout, $fastio2, $ssl2,
$authmech2, $authuser2, $reconnectretry2);
# history
$debug and print "From Buffer I/O: ", $from->Buffer(), "\n";
$debug and print "To Buffer I/O: ", $to->Buffer(), "\n";
$debug and print "Host1 Buffer I/O: ", $imap1->Buffer(), "\n";
$debug and print "Host2 Buffer I/O: ", $imap2->Buffer(), "\n";
sub login_imap {
@ -856,24 +856,24 @@ sub server_banner {
$debug and print "From capability: ", join(" ", $from->capability()), "\n";
$debug and print "To capability: ", join(" ", $to->capability()), "\n";
$debug and print "Host1 capability: ", join(" ", $imap1->capability()), "\n";
$debug and print "Host2 capability: ", join(" ", $imap2->capability()), "\n";
die unless $from->IsAuthenticated();
die unless $imap1->IsAuthenticated();
print "host1: state Authenticated\n";
die unless $to->IsAuthenticated();
die unless $imap2->IsAuthenticated();
print "host2: state Authenticated\n";
exit(0) if ($justlogin);
$split1 and $from->Split($split1);
$split2 and $to->Split($split2);
$split1 and $imap1->Split($split1);
$split2 and $imap2->Split($split2);
#
# Folder stuff
#
my (@f_folders, %requested_folder, @t_folders, @t_folders_list, %t_folders_list, %subscribed_folder, %t_folders);
my (@h1_folders, %requested_folder, @h2_folders, @h2_folders_list, %h2_folders_list, %subscribed_folder, %h2_folders);
sub tests_folder_routines {
ok( !give_requested_folders() ,"no requested folders" );
@ -931,7 +931,7 @@ sub remove_from_requested_folders {
# Make a hash of subscribed folders in source server.
map { $subscribed_folder{$_} = 1 } $from->subscribed();
map { $subscribed_folder{$_} = 1 } $imap1->subscribed();
@ -950,7 +950,7 @@ if (scalar(@folder) or $subscribed or scalar(@folderrec)) {
# option --folderrec
if (scalar(@folderrec)) {
foreach my $folderrec (@folderrec) {
add_to_requested_folders($from->folders($folderrec));
add_to_requested_folders($imap1->folders($folderrec));
}
}
}
@ -958,7 +958,7 @@ else {
# no include, no folder/subscribed/folderrec options => all folders
if (not scalar(@include)) {
my @all_source_folders = sort $from->folders();
my @all_source_folders = sort $imap1->folders();
add_to_requested_folders(@all_source_folders);
}
}
@ -966,7 +966,7 @@ else {
# consider (optional) includes and excludes
if (scalar(@include)) {
my @all_source_folders = sort $from->folders();
my @all_source_folders = sort $imap1->folders();
foreach my $include (@include) {
my @included_folders = grep /$include/, @all_source_folders;
add_to_requested_folders(@included_folders);
@ -986,7 +986,7 @@ if (scalar(@exclude)) {
# Remove no selectable folders
foreach my $folder (keys(%requested_folder)) {
if ( not $from->selectable($folder)) {
if ( not $imap1->selectable($folder)) {
print "Warning: ignoring folder $folder because it is not selectable\n";
remove_from_requested_folders($folder);
}
@ -995,7 +995,7 @@ foreach my $folder (keys(%requested_folder)) {
my @requested_folder = sort(keys(%requested_folder));
@f_folders = @requested_folder;
@h1_folders = @requested_folder;
sub compare_lists {
my ($list_1_ref, $list_2_ref) = @_;
@ -1071,22 +1071,22 @@ sub tests_compare_lists {
my($f_sep,$t_sep);
my($h1_sep,$h2_sep);
# what are the private folders separators for each server ?
$debug and print "Getting separators\n";
$f_sep = get_separator($from, $sep1, "--sep1");
$t_sep = get_separator($to, $sep2, "--sep2");
$h1_sep = get_separator($imap1, $sep1, "--sep1");
$h2_sep = get_separator($imap2, $sep2, "--sep2");
#my $f_namespace = $from->namespace();
#my $t_namespace = $to->namespace();
#$debug and print "From namespace:\n", Data::Dumper->Dump([$f_namespace]);
#$debug and print "To namespace:\n", Data::Dumper->Dump([$t_namespace]);
#my $h1_namespace = $imap1->namespace();
#my $h2_namespace = $imap2->namespace();
#$debug and print "Host1 namespace:\n", Data::Dumper->Dump([$h1_namespace]);
#$debug and print "Host2 namespace:\n", Data::Dumper->Dump([$h2_namespace]);
my($f_prefix,$t_prefix);
$f_prefix = get_prefix($from, $prefix1, "--prefix1");
$t_prefix = get_prefix($to, $prefix2, "--prefix2");
my($h1_prefix,$h2_prefix);
$h1_prefix = get_prefix($imap1, $prefix1, "--prefix1");
$h2_prefix = get_prefix($imap2, $prefix2, "--prefix2");
sub get_prefix {
my($imap, $prefix_in, $prefix_opt) = @_;
@ -1143,8 +1143,8 @@ sub get_separator {
}
print "From separator and prefix: [$f_sep][$f_prefix]\n";
print "To separator and prefix: [$t_sep][$t_prefix]\n";
print "Host1 separator and prefix: [$h1_sep][$h1_prefix]\n";
print "Host2 separator and prefix: [$h2_sep][$h2_prefix]\n";
sub foldersizes {
@ -1201,17 +1201,17 @@ sub foldersizes {
}
foreach my $f_fold (@f_folders) {
my $t_fold;
$t_fold = to_folder_name($f_fold);
$t_folders{$t_fold}++;
foreach my $h1_fold (@h1_folders) {
my $h2_fold;
$h2_fold = to_folder_name($h1_fold);
$h2_folders{$h2_fold}++;
}
@t_folders = sort keys(%t_folders);
@h2_folders = sort keys(%h2_folders);
if ($foldersizes) {
foldersizes("From", $from, \@f_folders);
foldersizes("To ", $to, \@t_folders);
foldersizes("Host1", $imap1, \@h1_folders);
foldersizes("Host2", $imap2, \@h2_folders);
}
@ -1229,21 +1229,21 @@ sub timenext {
exit if ($justfoldersizes);
# needed for setting flags
my $tohasuidplus = $to->has_capability("UIDPLUS");
my $imap2hasuidplus = $imap2->has_capability("UIDPLUS");
@t_folders_list = sort @{$to->folders()};
foreach my $folder (@t_folders_list) {
$t_folders_list{$folder}++;
@h2_folders_list = sort @{$imap2->folders()};
foreach my $folder (@h2_folders_list) {
$h2_folders_list{$folder}++;
}
print
"++++ Listing folders ++++\n",
"From folders list:\n", map("[$_]\n",@f_folders),"\n",
"To folders list:\n", map("[$_]\n",@t_folders_list),"\n";
"Host1 folders list:\n", map("[$_]\n",@h1_folders),"\n",
"Host2 folders list:\n", map("[$_]\n",@h2_folders_list),"\n";
print
"From subscribed folders list: ",
"Host1 subscribed folders list: ",
map("[$_] ", sort keys(%subscribed_folder)), "\n"
if ($subscribed);
@ -1251,35 +1251,35 @@ sub separator_invert {
# The separator we hope we'll never encounter: 00000000
my $o_sep="\000";
my($f_fold, $f_sep, $t_sep) = @_;
my($h1_fold, $h1_sep, $h2_sep) = @_;
my $t_fold = $f_fold;
$t_fold =~ s@\Q$t_sep@$o_sep@g;
$t_fold =~ s@\Q$f_sep@$t_sep@g;
$t_fold =~ s@\Q$o_sep@$f_sep@g;
return($t_fold);
my $h2_fold = $h1_fold;
$h2_fold =~ s@\Q$h2_sep@$o_sep@g;
$h2_fold =~ s@\Q$h1_sep@$h2_sep@g;
$h2_fold =~ s@\Q$o_sep@$h1_sep@g;
return($h2_fold);
}
sub to_folder_name {
my ($t_fold);
my ($h2_fold);
my ($x_fold) = @_;
# first we remove the prefix
$x_fold =~ s/^\Q$f_prefix\E//;
$x_fold =~ s/^\Q$h1_prefix\E//;
$debug and print "removed source prefix: [$x_fold]\n";
$t_fold = separator_invert($x_fold,$f_sep, $t_sep);
$debug and print "inverted separators: [$t_fold]\n";
$h2_fold = separator_invert($x_fold,$h1_sep, $h2_sep);
$debug and print "inverted separators: [$h2_fold]\n";
# Adding the prefix supplied by namespace or the --prefix2 option
$t_fold = $t_prefix . $t_fold
unless(($t_prefix eq "INBOX" . $t_sep) and ($t_fold =~ m/^INBOX$/i));
$debug and print "added target prefix: [$t_fold]\n";
$h2_fold = $h2_prefix . $h2_fold
unless(($h2_prefix eq "INBOX" . $h2_sep) and ($h2_fold =~ m/^INBOX$/i));
$debug and print "added target prefix: [$h2_fold]\n";
# Transforming the folder name by the --regextrans2 option(s)
foreach my $regextrans2 (@regextrans2) {
$debug and print "eval \$t_fold =~ $regextrans2\n";
eval("\$t_fold =~ $regextrans2");
$debug and print "eval \$h2_fold =~ $regextrans2\n";
eval("\$h2_fold =~ $regextrans2");
die("error: eval regextrans2 '$regextrans2': $@\n") if $@;
}
return($t_fold);
return($h2_fold);
}
sub tests_flags_regex {
@ -1313,21 +1313,21 @@ sub flags_regex {
}
sub acls_sync {
my($f_fold, $t_fold) = @_;
my($h1_fold, $h2_fold) = @_;
if ($syncacls) {
my $f_hash = $from->getacl($f_fold)
or warn "Could not getacl for $f_fold: $@\n";
my $t_hash = $to->getacl($t_fold)
or warn "Could not getacl for $t_fold: $@\n";
my %users = map({ ($_, 1) } (keys(%$f_hash), keys(%$t_hash)));
my $h1_hash = $imap1->getacl($h1_fold)
or warn "Could not getacl for $h1_fold: $@\n";
my $h2_hash = $imap2->getacl($h2_fold)
or warn "Could not getacl for $h2_fold: $@\n";
my %users = map({ ($_, 1) } (keys(%$h1_hash), keys(%$h2_hash)));
foreach my $user (sort(keys(%users))) {
my $acl = $f_hash->{$user} || "none";
my $acl = $h1_hash->{$user} || "none";
print "acl $user: [$acl]\n";
next if ($f_hash->{$user} && $t_hash->{$user} &&
$f_hash->{$user} eq $t_hash->{$user});
next if ($h1_hash->{$user} && $h2_hash->{$user} &&
$h1_hash->{$user} eq $h2_hash->{$user});
unless ($dry) {
print "setting acl $t_fold $user $acl\n";
$to->setacl($t_fold, $user, $acl)
print "setting acl $h2_fold $user $acl\n";
$imap2->setacl($h2_fold, $user, $acl)
or warn "Could not set acl: $@\n";
}
}
@ -1393,30 +1393,30 @@ sub flags_filter {
print "++++ Looping on each folder ++++\n";
FOLDER: foreach my $f_fold (@f_folders) {
my $t_fold;
print "From Folder [$f_fold]\n";
$t_fold = to_folder_name($f_fold);
print "To Folder [$t_fold]\n";
FOLDER: foreach my $h1_fold (@h1_folders) {
my $h2_fold;
print "Host1 Folder [$h1_fold]\n";
$h2_fold = to_folder_name($h1_fold);
print "Host2 Folder [$h2_fold]\n";
last FOLDER if $from->IsUnconnected();
last FOLDER if $to->IsUnconnected();
last FOLDER if $imap1->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
unless ($from->select($f_fold)) {
unless ($imap1->select($h1_fold)) {
warn
"From Folder $f_fold: Could not select: ",
$from->LastError, "\n";
"Host1 Folder $h1_fold: Could not select: ",
$imap1->LastError, "\n";
$error++;
next FOLDER;
}
if ( ! exists($t_folders_list{$t_fold})) {
print "To Folder $t_fold does not exist\n";
print "Creating folder [$t_fold]\n";
if ( ! exists($h2_folders_list{$h2_fold})) {
print "Host2 folder $h2_fold does not exist\n";
print "Creating folder [$h2_fold]\n";
unless ($dry){
unless ($to->create($t_fold)){
warn "Couldn't create [$t_fold]: ",
$to->LastError,"\n";
unless ($imap2->create($h2_fold)){
warn "Couldn't create [$h2_fold]: ",
$imap2->LastError,"\n";
$error++;
next FOLDER;
}
@ -1426,186 +1426,186 @@ FOLDER: foreach my $f_fold (@f_folders) {
}
}
acls_sync($f_fold, $t_fold);
acls_sync($h1_fold, $h2_fold);
unless ($to->select($t_fold)) {
unless ($imap2->select($h2_fold)) {
warn
"To Folder $t_fold: Could not select: ",
$to->LastError, "\n";
"Host2 folder $h2_fold: Could not select: ",
$imap2->LastError, "\n";
$error++;
next FOLDER;
}
my @select_results = $to->Results();
my @select_results = $imap2->Results();
#print "%%% @select_results\n";
my $permanentflags2 = permanentflags(@select_results);
if ($expunge){
print "Expunging host1 $f_fold\n";
unless($dry) { $from->expunge() };
#print "Expunging host2 $t_fold\n";
#unless($dry) { $to->expunge() };
print "Expunging host1 $h1_fold\n";
unless($dry) { $imap1->expunge() };
#print "Expunging host2 $h2_fold\n";
#unless($dry) { $imap2->expunge() };
}
if ($subscribe and exists $subscribed_folder{$f_fold}) {
print "Subscribing to folder $t_fold on destination server\n";
unless($dry) { $to->subscribe($t_fold) };
if ($subscribe and exists $subscribed_folder{$h1_fold}) {
print "Subscribing to folder $h2_fold on destination server\n";
unless($dry) { $imap2->subscribe($h2_fold) };
}
next FOLDER if ($justfolders);
last FOLDER if $from->IsUnconnected();
last FOLDER if $to->IsUnconnected();
last FOLDER if $imap1->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
my @f_msgs = select_msgs($from);
my @h1_msgs = select_msgs($imap1);
$debug and print "LIST FROM: ", scalar(@f_msgs), " messages [@f_msgs]\n";
$debug and print "LIST FROM: ", scalar(@h1_msgs), " messages [@h1_msgs]\n";
# internal dates on "TO" are after the ones on "FROM"
# normally...
my @t_msgs = select_msgs($to);
my @h2_msgs = select_msgs($imap2);
$debug and print "LIST TO : ", scalar(@t_msgs), " messages [@t_msgs]\n";
$debug and print "LIST TO : ", scalar(@h2_msgs), " messages [@h2_msgs]\n";
my %f_hash = ();
my %t_hash = ();
my %h1_hash = ();
my %h2_hash = ();
#print "++++ Using cache ++++\n";
print "++++ From [$f_fold] Parse 1 ++++\n";
last FOLDER if $from->IsUnconnected();
last FOLDER if $to->IsUnconnected();
print "++++ Host1 [$h1_fold] Parse 1 ++++\n";
last FOLDER if $imap1->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
my ($f_heads, $f_fir) = ({}, {});
$f_heads = $from->parse_headers([@f_msgs], @useheader) if (@f_msgs);
my ($h1_heads, $h1_fir) = ({}, {});
$h1_heads = $imap1->parse_headers([@h1_msgs], @useheader) if (@h1_msgs);
$debug and print "Time headers: ", timenext(), " s\n";
last FOLDER if $from->IsUnconnected();
last FOLDER if $imap1->IsUnconnected();
$f_fir = $from->fetch_hash("FLAGS", "INTERNALDATE", "RFC822.SIZE")
if (@f_msgs);
$h1_fir = $imap1->fetch_hash("FLAGS", "INTERNALDATE", "RFC822.SIZE")
if (@h1_msgs);
$debug and print "Time fir: ", timenext(), " s\n";
unless ($f_fir) {
unless ($h1_fir) {
warn
"From Folder $f_fold: Could not fetch_hash ",
scalar(@f_msgs), " msgs: ", $from->LastError, "\n";
"Host1 Folder $h1_fold: Could not fetch_hash ",
scalar(@h1_msgs), " msgs: ", $imap1->LastError, "\n";
$error++;
next FOLDER;
}
last FOLDER if $from->IsUnconnected();
last FOLDER if $imap1->IsUnconnected();
foreach my $m (@f_msgs) {
my $rc = parse_header_msg1($from, $m, $f_heads, $f_fir, "F", \%f_hash);
foreach my $m (@h1_msgs) {
my $rc = parse_header_msg1($imap1, $m, $h1_heads, $h1_fir, "F", \%h1_hash);
if (!$rc) {
my $reason = !defined($rc) ? "no header" : "duplicate";
my $f_size = $f_fir->{$m}->{"RFC822.SIZE"} || 0;
print "+ Skipping msg #$m:$f_size in folder $f_fold ($reason so we ignore this message)\n";
$mess_size_total_skipped += $f_size;
my $h1_size = $h1_fir->{$m}->{"RFC822.SIZE"} || 0;
print "+ Skipping msg #$m:$h1_size in folder $h1_fold ($reason so we ignore this message)\n";
$mess_size_total_skipped += $h1_size;
$mess_skipped += 1;
}
}
$debug and print "Time headers: ", timenext(), " s\n";
print "++++ To [$t_fold] Parse 1 ++++\n";
print "++++ Host2 [$h2_fold] Parse 1 ++++\n";
my ($t_heads, $t_fir) = ({}, {});
$t_heads = $to->parse_headers([@t_msgs], @useheader) if (@t_msgs);
my ($h2_heads, $h2_fir) = ({}, {});
$h2_heads = $imap2->parse_headers([@h2_msgs], @useheader) if (@h2_msgs);
$debug and print "Time headers: ", timenext(), " s\n";
last FOLDER if $to->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
$t_fir = $to->fetch_hash("FLAGS", "INTERNALDATE", "RFC822.SIZE")
if (@t_msgs);
$h2_fir = $imap2->fetch_hash("FLAGS", "INTERNALDATE", "RFC822.SIZE")
if (@h2_msgs);
$debug and print "Time fir: ", timenext(), " s\n";
last FOLDER if $to->IsUnconnected();
foreach my $m (@t_msgs) {
my $rc = parse_header_msg1($to, $m, $t_heads, $t_fir, "T", \%t_hash);
last FOLDER if $imap2->IsUnconnected();
foreach my $m (@h2_msgs) {
my $rc = parse_header_msg1($imap2, $m, $h2_heads, $h2_fir, "T", \%h2_hash);
if (!$rc) {
my $reason = !defined($rc) ? "no header" : "duplicate";
my $t_size = $t_fir->{$m}->{"RFC822.SIZE"} || 0;
print "+ Skipping msg #$m:$t_size in 'to' folder $t_fold ($reason so we ignore this message)\n";
my $h2_size = $h2_fir->{$m}->{"RFC822.SIZE"} || 0;
print "+ Skipping msg #$m:$h2_size in 'to' folder $h2_fold ($reason so we ignore this message)\n";
#$mess_size_total_skipped += $msize;
#$mess_skipped += 1;
}
}
$debug and print "Time headers: ", timenext(), " s\n";
print "++++ Verifying [$f_fold] -> [$t_fold] ++++\n";
print "++++ Verifying [$h1_fold] -> [$h2_fold] ++++\n";
# messages in "from" that are not good in "to"
my @f_hash_keys_sorted_by_uid
= sort {$f_hash{$a}{'m'} <=> $f_hash{$b}{'m'}} keys(%f_hash);
my @h1_hash_keys_sorted_by_uid
= sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys(%h1_hash);
#print map { $f_hash{$_}{'m'} . " "} @f_hash_keys_sorted_by_uid;
#print map { $h1_hash{$_}{'m'} . " "} @h1_hash_keys_sorted_by_uid;
my @t_hash_keys_sorted_by_uid
= sort {$t_hash{$a}{'m'} <=> $t_hash{$b}{'m'}} keys(%t_hash);
my @h2_hash_keys_sorted_by_uid
= sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys(%h2_hash);
if($delete2) {
my @expunge;
foreach my $m_id (@t_hash_keys_sorted_by_uid) {
foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
#print "$m_id ";
unless (exists($f_hash{$m_id})) {
my $t_msg = $t_hash{$m_id}{'m'};
my $flags = $t_hash{$m_id}{'F'} || "";
unless (exists($h1_hash{$m_id})) {
my $h2_msg = $h2_hash{$m_id}{'m'};
my $flags = $h2_hash{$m_id}{'F'} || "";
my $isdel = $flags =~ /\B\\Deleted\b/ ? 1 : 0;
print "deleting message $m_id $t_msg\n"
print "deleting message $m_id $h2_msg\n"
if ! $isdel;
push(@expunge,$t_msg) if $uidexpunge2;
push(@expunge,$h2_msg) if $uidexpunge2;
unless ($dry or $isdel) {
$to->delete_message($t_msg);
last FOLDER if $to->IsUnconnected();
$imap2->delete_message($h2_msg);
last FOLDER if $imap2->IsUnconnected();
}
}
}
my $cnt = scalar @expunge;
if(@expunge and !$to->can("uidexpunge")) {
if(@expunge and !$imap2->can("uidexpunge")) {
warn "uidexpunge not supported (< IMAPClient 3.17)\n";
}
elsif(@expunge) {
print "uidexpunge $cnt message(s)\n";
$to->uidexpunge(\@expunge) if !$dry;
$imap2->uidexpunge(\@expunge) if !$dry;
}
}
MESS: foreach my $m_id (@f_hash_keys_sorted_by_uid) {
my $f_size = $f_hash{$m_id}{'s'};
my $f_msg = $f_hash{$m_id}{'m'};
my $f_idate = $f_hash{$m_id}{'D'};
MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) {
my $h1_size = $h1_hash{$m_id}{'s'};
my $h1_msg = $h1_hash{$m_id}{'m'};
my $h1_idate = $h1_hash{$m_id}{'D'};
if (defined $maxsize and $f_size > $maxsize) {
print "+ Skipping msg #$f_msg:$f_size in folder $f_fold (exceeds maxsize limit $maxsize bytes)\n";
$mess_size_total_skipped += $f_size;
if (defined $maxsize and $h1_size > $maxsize) {
print "+ Skipping msg #$h1_msg:$h1_size in folder $h1_fold (exceeds maxsize limit $maxsize bytes)\n";
$mess_size_total_skipped += $h1_size;
$mess_skipped += 1;
next MESS;
}
$debug and print "+ key $m_id #$f_msg\n";
unless (exists($t_hash{$m_id})) {
print "+ NO msg #$f_msg [$m_id] in $t_fold\n";
$debug and print "+ key $m_id #$h1_msg\n";
unless (exists($h2_hash{$m_id})) {
print "+ NO msg #$h1_msg [$m_id] in $h2_fold\n";
# copy
print "+ Copying msg #$f_msg:$f_size to folder $t_fold\n";
last FOLDER if $from->IsUnconnected();
last FOLDER if $to->IsUnconnected();
print "+ Copying msg #$h1_msg:$h1_size to folder $h2_fold\n";
last FOLDER if $imap1->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
my $string;
$string = $from->message_string($f_msg);
$string = $imap1->message_string($h1_msg);
unless (defined($string)) {
warn
"Could not fetch message #$f_msg from $f_fold: ",
$from->LastError, "\n";
"Could not fetch message #$h1_msg from $h1_fold: ",
$imap1->LastError, "\n";
$error++;
$mess_size_total_error += $f_size;
$mess_size_total_error += $h1_size;
next MESS;
}
#print "AAAmessage_string[$string]ZZZ\n";
#my $message_file = "tmp_imapsync_$$";
#$from->select($f_fold);
#$imap1->select($h1_fold);
#unlink($message_file);
#$from->message_to_file($message_file, $f_msg) or do {
# warn "Could not put message #$f_msg to file $message_file",
# $from->LastError;
#$imap1->message_to_file($message_file, $h1_msg) or do {
# warn "Could not put message #$h1_msg to file $message_file",
# $imap1->LastError;
# $error++;
# $mess_size_total_error += $f_size;
# $mess_size_total_error += $h1_size;
# next MESS;
#};
#$string = file_to_string($message_file);
@ -1649,7 +1649,7 @@ FOLDER: foreach my $f_fold (@f_folders) {
"F message content ended on previous line\n", "=" x 80, "\n";
my $d = "";
if ($syncinternaldates) {
$d = $f_idate;
$d = $h1_idate;
$debug and print "internal date from 1: [$d]\n";
$d = good_date($d);
$debug and print "internal date from 1: [$d] (fixed)\n";
@ -1657,7 +1657,7 @@ FOLDER: foreach my $f_fold (@f_folders) {
if ($idatefromheader) {
$d = $from->get_header($f_msg,"Date");
$d = $imap1->get_header($h1_msg,"Date");
$debug and print "header date from 1: [$d]\n";
$d = good_date($d);
$debug and print "header date from 1: [$d] (fixed)\n";
@ -1670,7 +1670,7 @@ FOLDER: foreach my $f_fold (@f_folders) {
return($d);
}
my $flags_f = $f_hash{$m_id}{'F'} || "";
my $flags_f = $h1_hash{$m_id}{'F'} || "";
# RFC 2060: This flag can not be altered by any client
$flags_f =~ s@\\Recent\s?@@gi;
$flags_f = flags_regex($flags_f) if @regexflag;
@ -1679,42 +1679,42 @@ FOLDER: foreach my $f_fold (@f_folders) {
my $new_id;
print "flags from: [$flags_f][$d]\n";
last FOLDER if $from->IsUnconnected();
last FOLDER if $to->IsUnconnected();
last FOLDER if $imap1->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
unless ($dry) {
if ($OSNAME eq "MSWin32") {
$new_id = $to->append_string($t_fold,$string, $flags_f, $d);
$new_id = $imap2->append_string($h2_fold,$string, $flags_f, $d);
}
else {
# just back to append_string since append_file 3.05 does not work.
#$new_id = $to->append_file($t_fold, $message_file, "", $flags_f, $d);
#$new_id = $imap2->append_file($h2_fold, $message_file, "", $flags_f, $d);
# append_string 3.05 does not work too some times with $d unset.
$new_id = $to->append_string($t_fold,$string, $flags_f, $d);
$new_id = $imap2->append_string($h2_fold,$string, $flags_f, $d);
}
unless($new_id){
no warnings 'uninitialized';
warn "Couldn't append msg #$f_msg (Subject:[".
$from->subject($f_msg)."]) to folder $t_fold: ",
$to->LastError, "\n";
warn "Couldn't append msg #$h1_msg (Subject:[".
$imap1->subject($h1_msg)."]) to folder $h2_fold: ",
$imap2->LastError, "\n";
$error++;
$mess_size_total_error += $f_size;
$mess_size_total_error += $h1_size;
next MESS;
}
else{
# good
# $new_id is an id if the IMAP server has the
# UIDPLUS capability else just a ref
print "Copied msg id [$f_msg] to folder $t_fold msg id [$new_id]\n";
$mess_size_total_trans += $f_size;
print "Copied msg id [$h1_msg] to folder $h2_fold msg id [$new_id]\n";
$mess_size_total_trans += $h1_size;
$mess_trans += 1;
if($delete) {
print "Deleting msg #$f_msg in folder $f_fold\n";
print "Deleting msg #$h1_msg in folder $h1_fold\n";
unless($dry) {
$from->delete_message($f_msg);
last FOLDER if $from->IsUnconnected();
$from->expunge() if ($expunge);
last FOLDER if $from->IsUnconnected();
$imap1->delete_message($h1_msg);
last FOLDER if $imap1->IsUnconnected();
$imap1->expunge() if ($expunge);
last FOLDER if $imap1->IsUnconnected();
}
}
}
@ -1726,19 +1726,19 @@ FOLDER: foreach my $f_fold (@f_folders) {
next MESS;
}
else{
$debug and print "Message id [$m_id] found in t:$t_fold\n";
$mess_size_total_skipped += $f_size;
$debug and print "Message id [$m_id] found in t:$h2_fold\n";
$mess_size_total_skipped += $h1_size;
$mess_skipped += 1;
}
$fast and next MESS;
#$debug and print "MESSAGE $m_id\n";
my $t_size = $t_hash{$m_id}{'s'};
my $t_msg = $t_hash{$m_id}{'m'};
my $h2_size = $h2_hash{$m_id}{'s'};
my $h2_msg = $h2_hash{$m_id}{'m'};
# used cached flag values for efficiency
my $flags_f = $f_hash{$m_id}{'F'} || "";
my $flags_t = $t_hash{$m_id}{'F'} || "";
my $flags_f = $h1_hash{$m_id}{'F'} || "";
my $flags_t = $h2_hash{$m_id}{'F'} || "";
# RFC 2060: This flag can not be altered by any client
$flags_f =~ s@\\Recent\s?@@gi;
@ -1749,29 +1749,29 @@ FOLDER: foreach my $f_fold (@f_folders) {
my %ft = map { $_ => 1 } split(' ', $flags_t );
my @flags_a = map { exists $ft{$_} ? () : $_ } @ff;
$debug and print "Setting flags(@flags_a) ffrom($flags_f) fto($flags_t) on msg #$t_msg in $t_fold\n";
$debug and print "Setting flags(@flags_a) ffrom($flags_f) fto($flags_t) on msg #$h2_msg in $h2_fold\n";
# This adds or changes flags but no flag are removed with this
if (!$dry and @flags_a and !$to->store($t_msg, "+FLAGS.SILENT (@flags_a)") ) {
if (!$dry and @flags_a and !$imap2->store($h2_msg, "+FLAGS.SILENT (@flags_a)") ) {
warn "Could not add flags '@flags_a' flagf '$flags_f'",
" flagt '$flags_t' on msg #$t_msg in $t_fold: ",
$to->LastError, "\n";
" flagt '$flags_t' on msg #$h2_msg in $h2_fold: ",
$imap2->LastError, "\n";
#$error++;
}
last FOLDER if $to->IsUnconnected();
last FOLDER if $imap2->IsUnconnected();
$debug and do {
my @flags_t = @{ $to->flags($t_msg) || [] };
last FOLDER if $to->IsUnconnected();
my @flags_t = @{ $imap2->flags($h2_msg) || [] };
last FOLDER if $imap2->IsUnconnected();
print "flags from: $flags_f\n",
"flags to : @flags_t\n";
print "Looking dates\n";
#my $d_f = $from->internaldate($f_msg);
#my $d_t = $to->internaldate($t_msg);
my $d_f = $f_hash{$m_id}{'D'};
my $d_t = $t_hash{$m_id}{'D'};
#my $d_f = $imap1->internaldate($h1_msg);
#my $d_t = $imap2->internaldate($h2_msg);
my $d_f = $h1_hash{$m_id}{'D'};
my $d_t = $h2_hash{$m_id}{'D'};
print
"idate from: $d_f\n",
"idate to : $d_t\n";
@ -1780,41 +1780,41 @@ FOLDER: foreach my $f_fold (@f_folders) {
# print "!!! Dates differ !!!\n";
#}
};
unless (($f_size == $t_size) or $skipsize) {
unless (($h1_size == $h2_size) or $skipsize) {
# Bad size
print
"Message $m_id SZ_BAD f:$f_msg:$f_size t:$t_msg:$t_size\n";
"Message $m_id SZ_BAD f:$h1_msg:$h1_size t:$h2_msg:$h2_size\n";
# delete in to and recopy ?
# NO recopy CODE HERE. to be written if needed.
$error++;
if ($opt_G){
print "Deleting msg f:#$t_msg in folder $t_fold\n";
$to->delete_message($t_msg) unless ($dry);
last FOLDER if $to->IsUnconnected();
print "Deleting msg f:#$h2_msg in folder $h2_fold\n";
$imap2->delete_message($h2_msg) unless ($dry);
last FOLDER if $imap2->IsUnconnected();
}
}
else {
# Good
$debug and print
"Message $m_id SZ_GOOD f:$f_msg:$f_size t:$t_msg:$t_size\n";
"Message $m_id SZ_GOOD f:$h1_msg:$h1_size t:$h2_msg:$h2_size\n";
if($delete) {
print "Deleting msg #$f_msg in folder $f_fold\n";
print "Deleting msg #$h1_msg in folder $h1_fold\n";
unless($dry) {
$from->delete_message($f_msg);
last FOLDER if $from->IsUnconnected();
$from->expunge() if ($expunge);
last FOLDER if $from->IsUnconnected();
$imap1->delete_message($h1_msg);
last FOLDER if $imap1->IsUnconnected();
$imap1->expunge() if ($expunge);
last FOLDER if $imap1->IsUnconnected();
}
}
}
}
if ($expunge1){
print "Expunging source folder $f_fold\n";
unless($dry) { $from->expunge() };
print "Expunging source folder $h1_fold\n";
unless($dry) { $imap1->expunge() };
}
if ($expunge2){
print "Expunging target folder $t_fold\n";
unless($dry) { $to->expunge() };
print "Expunging target folder $h2_fold\n";
unless($dry) { $imap2->expunge() };
}
print "Time: ", timenext(), " s\n";
@ -1825,7 +1825,7 @@ print "++++ End looping on each folder ++++\n";
# FOLDER loop is exited any time a connection is lost be sure to log it!
# Example:
# lost_connection($from,"(from) host1 [$host1]");
# lost_connection($imap1,"host1 [$host1]");
#
# can be tested with a "killall /usr/bin/imapd" (or equivalent) in command line.
#
@ -1860,8 +1860,8 @@ sub lost_connection {
}
}
$from->logout() unless (lost_connection($from,"(from) host1 [$host1]"));
$to->logout() unless (lost_connection($to,"(to) host2 [$host2]"));
$imap1->logout() unless (lost_connection($imap1,"host1 [$host1]"));
$imap2->logout() unless (lost_connection($imap2,"host2 [$host2]"));
$timeend = time();