From 3f8607bd963d427a0c025cd15b5102c271b03465 Mon Sep 17 00:00:00 2001
From: Nick Bebout
Date: Sat, 12 Mar 2011 02:45:02 +0000
Subject: [PATCH] 1.366
---
CONCEPTION | 26 +
CREDITS | 51 +-
ChangeLog | 81 +-
FAQ | 23 +-
INSTALL | 17 +-
Makefile | 31 +-
README | 19 +-
TIME | 17 +
TODO | 26 +-
VERSION | 2 +-
VERSION_EXE | 2 +-
build_exe.bat | 4 +-
freshmeat_submition.inp | 11 +-
freshmeat_submition.json | 28 +-
imapsync | 2352 +++++++++--------
index.shtml | 125 +-
learn/imapclient3xx_skeleton_test | 2 +-
learn/io_socket_get | 0
learn/memory_consumption | 57 +
learn/message_string_raw | 110 +
learn/message_string_raw_pb | 209 ++
learn/mi2 | 4 +
learn/mi3 | 4 +
logo_imapsync.png | Bin 0 -> 38832 bytes
logo_imapsync_2.svg | 149 ++
logo_imapsync_s.png | Bin 0 -> 3957 bytes
memo | 19 +
patches/imapsync_minsize | 4079 +++++++++++++++++++++++++++++
test.bat | 15 +-
test_exe.bat | 5 +-
test_exe_2.bat | 2 +
tests.sh | 394 +--
32 files changed, 6408 insertions(+), 1456 deletions(-)
create mode 100644 CONCEPTION
mode change 100644 => 100755 learn/io_socket_get
create mode 100755 learn/memory_consumption
create mode 100755 learn/message_string_raw
create mode 100755 learn/message_string_raw_pb
create mode 100755 learn/mi2
create mode 100755 learn/mi3
create mode 100644 logo_imapsync.png
create mode 100644 logo_imapsync_2.svg
create mode 100644 logo_imapsync_s.png
create mode 100644 patches/imapsync_minsize
create mode 100644 test_exe_2.bat
diff --git a/CONCEPTION b/CONCEPTION
new file mode 100644
index 0000000..a786035
--- /dev/null
+++ b/CONCEPTION
@@ -0,0 +1,26 @@
+
+===== Synopsis =====
+
+$mailbox_1 = Mail::imapsync::mailbox->new();
+$mailbox_2 = Mail::imapsync::mailbox->new();
+
+
+$mailbox_1->host('imap1.lala.org');
+$mailbox_1->user('toto1');
+...
+
+$mailbox_2->host('imap2.lala.org');
+$mailbox_2->user('toto2');
+...
+
+
+$transfer = Mail::imapsync::transfer->new();
+$transfer->sync($mailbox_1, $mailbox_2);
+
+
+- an object for mailbox
+- an object for a transfer
+- ?an object for a folder?
+- ?an object for a message?
+
+
diff --git a/CREDITS b/CREDITS
index 881a2f5..1acafef 100644
--- a/CREDITS
+++ b/CREDITS
@@ -1,7 +1,8 @@
#!/bin/cat
-# $Id: CREDITS,v 1.144 2010/09/06 01:08:41 gilles Exp gilles $
+# $Id: CREDITS,v 1.150 2010/10/24 23:54:09 gilles Exp gilles $
-If you want to make a donation to the author, Gilles LAMIRAL:
+If you want to make a donation to the author, Gilles LAMIRAL,
+use any of the following ways:
a) you can use the imapsync wishlist :
http://www.amazon.com/gp/registry/wishlist/1C9UNDIH3P7R7/
@@ -14,12 +15,50 @@ b) If you can read french, please use the following wishlist :
c) its paypal account : gilles.lamiral@laposte.net
http://www.linux-france.org/prj/imapsync/paypal.html
-Here are the persons who helped me to develop imapsync.
-Feel free to tell me if a name is missing or if you want
-to remove one.
+d) If you prefer making your donation with
+cash or cheque then my postal address is:
+
+Gilles LAMIRAL
+4 La Billais
+35580 Baulon
+FRANCE
+
+Here are the persons who helped me to develop and maintain imapsync.
+Feel free to tell me if a name is missing or if you want to remove one.
I thank very much all of these people.
+Roger Schmid
+Contributed by giving money 100 USD
+
+Danny Schulz
+Contributed by giving money 15 USD
+
+Christian Kowarzik
+Contributed by giving money 90 USD
+for --deletefolder2 option.
+
+Harald Petrovitsch
+Contributed by giving the book
+29.95 "Families and How to Survive Them"
+
+Tobias Fink
+Contributed by giving money 5 Eur
+
+Yanick Cyr
+Contributed by giving money 25 USD
+
+Trony Tigno
+Contributed by giving money 5 USD
+
+Paul Garner
+Contributed by giving money 5 USD
+
+Kevin Kretz
+Contributed by giving the books
+15.25 "Tres Cubano: A Complete Guide To Playing The Cuban Tres Guitar (Book & CD)"
+24.00 "Creative Clowning"
+
Kirk Ismay
Contributed by giving money 50 USD
@@ -907,6 +946,8 @@ Eric Yung
Total amount of book prices :
c \
+29.95
+\
11.20+\
24.95+\
13.57+\
diff --git a/ChangeLog b/ChangeLog
index 53083e3..b2e7178 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,17 +1,90 @@
RCS file: RCS/imapsync,v
Working file: imapsync
-head: 1.350
+head: 1.366
branch:
locks: strict
- gilles: 1.350
+ gilles: 1.366
access list:
symbolic names:
keyword substitution: kv
-total revisions: 350; selected revisions: 350
+total revisions: 366; selected revisions: 366
description:
----------------------------
-revision 1.350 locked by: gilles;
+revision 1.366 locked by: gilles;
+date: 2010/10/25 17:15:52; author: gilles; state: Exp; lines: +11 -12
+Permit host* to have change the case of headers.
+----------------------------
+revision 1.365
+date: 2010/10/25 11:42:41; author: gilles; state: Exp; lines: +131 -157
+Fix tls getline pb (read too early)
+Changes place of starttls() myconnect() and other functions.
+----------------------------
+revision 1.364
+date: 2010/10/25 09:56:27; author: gilles; state: Exp; lines: +10 -15
+Fix STARTTLS capability detection bug.
+----------------------------
+revision 1.363
+date: 2010/10/24 17:16:43; author: gilles; state: Exp; lines: +99 -48
+Added --delete2folders option. Deletes folders in host2 that are not in host1.
+----------------------------
+revision 1.362
+date: 2010/10/22 19:23:34; author: gilles; state: Exp; lines: +24 -8
+Added imapsync basename to see how imapsync.exe is used.
+----------------------------
+revision 1.361
+date: 2010/10/19 22:58:06; author: gilles; state: Exp; lines: +28 -8
+Added --nomodules_version option to avoid Roger libeay32.dll missing problem.
+Added test to ckeck if the release number from lfo VERSION file is a number.
+----------------------------
+revision 1.360
+date: 2010/10/19 22:08:23; author: gilles; state: Exp; lines: +8 -7
+Better documentation to subscribe to the imapsync list.
+----------------------------
+revision 1.359
+date: 2010/10/08 01:17:29; author: gilles; state: Exp; lines: +15 -9
+Fixed "Your vendor has not defined POSIX macro SIGALRM" bug on win32.
+----------------------------
+revision 1.358
+date: 2010/10/08 00:40:42; author: gilles; state: Exp; lines: +97 -18
+Add memory_consumption for win32.
+----------------------------
+revision 1.357
+date: 2010/10/04 21:50:56; author: gilles; state: Exp; lines: +10 -10
+Suppressed ref passage in foldersizes() sub.
+----------------------------
+revision 1.356
+date: 2010/10/04 02:44:00; author: gilles; state: Exp; lines: +1270 -1251
+Move all subroutines below main.
+Changed "local $SIG{ALRM}" to "POSIX::sigaction(SIGALRM"
+----------------------------
+revision 1.355
+date: 2010/09/21 01:50:34; author: gilles; state: Exp; lines: +39 -19
+Added tests_max() max() functions.
+Added memory consumption.
+Added memory consumption ratio to biggest message transfered.
+----------------------------
+revision 1.354
+date: 2010/09/16 00:25:20; author: gilles; state: Exp; lines: +27 -15
+Added memory_consumption_ratio()
+Added memory_consumption_of_pid()
+Removed memory_consumption
+----------------------------
+revision 1.353
+date: 2010/09/14 22:46:33; author: gilles; state: Exp; lines: +19 -9
+Added --minsize option.
+----------------------------
+revision 1.352
+date: 2010/09/14 21:53:55; author: gilles; state: Exp; lines: +48 -12
+Added memory_consumption()
+Added tests_memory_consumption()
+Started to analyse memory consumption.
+----------------------------
+revision 1.351
+date: 2010/09/06 16:28:17; author: gilles; state: Exp; lines: +9 -7
+Fixed PERL_VERSION format in imapsync_version_lfo()
+----------------------------
+revision 1.350
date: 2010/09/06 01:05:09; author: gilles; state: Exp; lines: +33 -24
Added --noreleasecheck option.
Added User-agent information (OS, perl version, Mail::IMAPClient version)
diff --git a/FAQ b/FAQ
index 748460b..a571592 100644
--- a/FAQ
+++ b/FAQ
@@ -1,5 +1,5 @@
#!/bin/cat
-# $Id: FAQ,v 1.73 2010/08/08 23:09:04 gilles Exp gilles $
+# $Id: FAQ,v 1.75 2010/10/19 23:31:10 gilles Exp gilles $
+------------------+
| FAQ for imapsync |
@@ -595,6 +595,14 @@ Examples:
3) to substitute all characters dot "." by underscores "_"
--regextrans2 's/\./_/g'
+4) to change folder names like this:
+[mail/Sent Items] -> [Sent]
+[mail/Test] -> [INBOX/Test]
+[mail/Test2] -> [INBOX/Test2]
+
+ --regextrans2 's#^mail/Sent Items$#Sent#' \
+ --regextrans2 's#^mail/#INBOX/#'
+
=======================================================================
Q. I would like to move emails from InBox to a sub-folder called,
say "2005-InBox" based on the date (Like all emails received in the
@@ -755,13 +763,16 @@ Q. Synchronising from Gmail to XXX
R. Gmail needs SSL
./imapsync \
- --host1 imap.gmail.com --ssl1 \
+ --host1 imap.gmail.com \
+ --ssl1 \
+ --authmech1 LOGIN \
--user1 gilles.lamiral@gmail.com \
- --passfile1 /var/tmp/secret.gilles_gmail \
+ --password1 gmailsecret \
--host2 localhost
- --user2 tata@est.belle \
- --passfile2 /var/tmp/secret.tata \
- --useheader="X-Gmail-Received" --skipsize
+ --user2 tata \
+ --password2 tatasecret \
+ --useheader="X-Gmail-Received" \
+ --useheader 'Message-Id'
If your destination imap server doesn't like "[Gmail]" name, just add
option:
diff --git a/INSTALL b/INSTALL
index 412fca8..bd1b070 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,4 +1,4 @@
-# $Id: INSTALL,v 1.17 2010/07/16 22:01:57 gilles Exp gilles $
+# $Id: INSTALL,v 1.18 2010/10/25 09:32:49 gilles Exp gilles $
#
# INSTALL file for imapsync
# imapsync : IMAP sync or copy tool.
@@ -84,9 +84,6 @@ Here is some individual module help:
- IO:Socket:SSL.pm
perl -mIO::Socket::SSL -e ''
-- Date::Manip
- perl -mDate::Manip -e ''
-
- File::Spec
perl -mFile::Spec -e ''
@@ -98,7 +95,7 @@ Here is some individual module help:
Everything in one command:
- perl -mMail::IMAPClient -mDigest::MD5 -mTerm::ReadKey -mIO::Socket::SSL -mDate::Manip -mFile::Spec -mDigest::HMAC_MD5 -e ''
+ perl -mMail::IMAPClient -mDigest::MD5 -mTerm::ReadKey -mIO::Socket::SSL -mFile::Spec -mDigest::HMAC_MD5 -e ''
INSTALLING
----------
@@ -115,10 +112,14 @@ or copy the file imapsync where you want it to be.
WINDOWS
-------
+a) Simplest way:
+
+- Use imapsync.exe
+
+b) Hard way:
+
- Install Perl if it isn't already installed.
- ActivePerl from ActiveState is a good candidate if
- you understand nothing at free/open software
- and want to run imapsync with success.
+ Strawberry Perl is a good candidate
- Use PPM to install modules listed in the PREREQUISITES section.
PPM is Perl Package Manager.
diff --git a/Makefile b/Makefile
index 8f657ab..932d96f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
-# $Id: Makefile,v 1.37 2010/08/24 01:46:36 gilles Exp gilles $
+# $Id: Makefile,v 1.42 2010/10/24 23:52:31 gilles Exp gilles $
.PHONY: help usage all
@@ -15,6 +15,7 @@ usage:
@echo "make all "
@echo "make upload_index"
@echo "make imapsync.exe"
+ @echo "make upload_imapsync_exe"
DIST_NAME=imapsync-$(VERSION)
@@ -137,6 +138,9 @@ upload_index: index.shtml
../../public_html/www.linux-france.org/html/prj/imapsync/
sh $(HOME)/memo/lfo-rsync
+
+
+
.dosify_bat: build_exe.bat test_exe.bat test.bat
unix2dos build_exe.bat test.bat test_exe.bat
touch .dosify_bat
@@ -149,18 +153,39 @@ dosify_bat: .dosify_bat
imapsync_cidone: .imapsync_cidone
+copy_win32:
+ scp imapsync Admin@c:'C:/msys/1.0/home/Admin/imapsync/'
+
+tests_win32: dosify_bat
+ scp imapsync test.bat Admin@c:'C:/msys/1.0/home/Admin/imapsync/'
+ ssh Admin@c 'perl C:/msys/1.0/home/Admin/imapsync/imapsync --tests_debug'
+# ssh Admin@c 'perl C:/msys/1.0/home/Admin/imapsync/imapsync'
+# ssh Admin@c 'C:/msys/1.0/home/Admin/imapsync/test.bat'
+# ssh Admin@c 'tasklist /FI "PID eq 0"'
+# ssh Admin@c 'tasklist /NH /FO CSV'
+
+upload_imapsync_exe:
+ rsync -avH imapsync.exe \
+ ../../public_html/www.linux-france.org/html/prj/imapsync/
+ #sh $(HOME)/memo/lfo-rsync
test_imapsync_exe: dosify_bat
scp test_exe.bat Admin@c:'C:/msys/1.0/home/Admin/imapsync/'
time ssh Admin@c 'C:/msys/1.0/home/Admin/imapsync/test_exe.bat'
imapsync.exe: imapsync imapsync_cidone dosify_bat
+ (date "+%s"| tr "\n" " "; echo -n "BEGIN " $(VERSION) ": "; date) >> .BUILD_EXE_TIME
scp imapsync build_exe.bat test_exe.bat \
Admin@c:'C:/msys/1.0/home/Admin/imapsync/'
time ssh Admin@c 'C:/msys/1.0/home/Admin/imapsync/build_exe.bat'
time ssh Admin@c 'C:/msys/1.0/home/Admin/imapsync/test_exe.bat'
scp Admin@c:'C:/msys/1.0/home/Admin/imapsync/imapsync.exe' .
ssh Admin@c 'C:/msys/1.0/home/Admin/imapsync/imapsync.exe --version' > VERSION_EXE
+ (date "+%s"| tr "\n" " "; echo -n "END " $(VERSION) ": "; date) >> .BUILD_EXE_TIME
+
+zzz:
+ (date "+%s"| tr "\n" " "; echo -n "BEGIN " $(VERSION) ": "; date) >> .BUILD_EXE_TIME
+ (date "+%s"| tr "\n" " "; echo -n "END " $(VERSION) ": "; date) >> .BUILD_EXE_TIME
lfo: dist niouze_lfo upload_lfo
@@ -173,10 +198,10 @@ upload_lfo:
sh ~/memo/lfo-rsync
niouze_lfo : VERSION
- . memo && lfo_announce
+ . ./memo && lfo_announce
niouze_fm: VERSION
- . memo && fm_announce
+ . ./memo && fm_announce
public: niouze_fm
diff --git a/README b/README
index d6be304..6709d9c 100644
--- a/README
+++ b/README
@@ -3,7 +3,7 @@ NAME
Synchronise mailboxes between two imap servers. Good at IMAP migration.
More than 36 different IMAP server softwares supported with success.
- $Revision: 1.350 $
+ $Revision: 1.366 $
SYNOPSIS
To synchronise imap account "foo" on "imap.truc.org" to imap account
@@ -16,12 +16,13 @@ SYNOPSIS
INSTALL
imapsync works fine under any Unix OS with perl.
- imapsync works fine under Windows (2000, XP) with ActiveState's 5.8 Perl
- or as a standalone binary software.
+ imapsync works fine under Windows (2000, XP)
+ with Strawberry Perl 5.10 or 5.12
+ or as a standalone binary software imapsync.exe
imapsync is already available directly on the following distributions
- (at least): FreeBSD, Debian, Ubuntu, Gentoo, NetBSD, Darwin, Mandriva
- and OpenBSD (yeah!).
+ (at least): FreeBSD, Debian, Ubuntu, Gentoo, Fedora, NetBSD, Darwin,
+ Mandriva and OpenBSD (yeah!).
Get imapsync at
http://www.linux-france.org/prj/imapsync/
@@ -70,6 +71,7 @@ USAGE
[--syncacls]
[--regexmess ] [--regexmess ]
[--maxsize ]
+ [--minsize ]
[--maxage ]
[--minage ]
[--skipheader ]
@@ -185,8 +187,9 @@ MAILING-LIST
To write on the mailing-list, the address is:
- To subscribe, send a message to:
-
+ To subscribe, send any message (even empty) to:
+ then just reply to the
+ confirmation message.
To unsubscribe, send a message to:
@@ -401,5 +404,5 @@ SIMILAR SOFTWARES
Feedback (good or bad) will often be welcome.
- $Id: imapsync,v 1.350 2010/09/06 01:05:09 gilles Exp gilles $
+ $Id: imapsync,v 1.366 2010/10/25 17:15:52 gilles Exp gilles $
diff --git a/TIME b/TIME
index da48536..10aaed3 100644
--- a/TIME
+++ b/TIME
@@ -1,3 +1,20 @@
+ 90 Permit host* to change the case of headers. 1.366 release.
+120 Fix tls capability. 1.365 release.
+150 1.363 public release.
+240 Added --delete2folders option.
+150 Try to fix again win32 libeay32.dll issue. Upload 1.361 and .exe
+270 Draw imapsync logo with inkscape. Had a crash after 2 hours of drawing and saved file was also buggy...
+120 Bugfix win32. Email. Added and tested linkage with --link libeay32.dll --link ssleay32.dll
+120 Bugfix about POSIX alarm on win32. imapsync 1.359 public release.
+160 Added memory consumption for win32. imapsync 1.358
+300 Local environment. Tests. Moved subroutines below main. Changed alarm call.
+ 90 Memory consumption on Win32
+ 50 Better memory consumption statistics
+ 35 Added memory consumption to final stats.
+240 Wrote a message_string_raw() function. Tests.
+120 Thinking about performance. Email to italian compagny.
+100 Tracking memory consumption. Wrote learn/memory_consumption. Sent bug report to bug-Mail-IMAPClient [at] rt.cpan.org.
+180 Tracking memory consumption.
360 Date::Manip away. good_date() rewriting. release check on lfo
210 Better output when copying messages. Profiling memory.
80 Gmail efficiency. Wanted! on homepage, email on list.
diff --git a/TODO b/TODO
index 4463230..689e8df 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,5 @@
#!/bin/cat
-# $Id: TODO,v 1.85 2010/09/06 01:08:14 gilles Exp gilles $
+# $Id: TODO,v 1.86 2010/10/08 00:43:09 gilles Exp gilles $
TODO file for imapsync
----------------------
@@ -13,6 +13,8 @@ Start a wiki for imapsync.
Add a best practice migration tips document.
+Write a Mail::imapsync package and use it.
+
Fix the mailing-list archive bug with From at
the beginning of a line
http://www.linux-france.org/prj/imapsync_list/msg00307.html
@@ -21,19 +23,11 @@ Evaluate
http://www.rackspace.com/apps/email_hosting/migrations
http://www.yippiemove.com/
-Evaluate memory consumption with (or better):
-print qx{ ps o pid,pcpu,comm,vsz,rss,size $$ }, "\n"
-Search memory leaks with
-Test-Weaken Test-Memory-Cycle Devel-Cycle Devel-Leak Test-Weaken
-sh -x tests.sh ll_bigmail
-is a good candidate to stress memory.
-
Suggestion: it's very difficult to track down messages which are behaving
funny during the sync. It would be great - and presumably easy to code -
to have an option to have imapsync display e.g. the subject of an
e-mail when it gets synced, rather than just the message ID and the date/time.
-
Add --noauthmd51 --noauthmd52 to permit noauthmd5 by host
Add a well described problem for each problem detected
@@ -109,13 +103,14 @@ Add a --skipheaderinfolder option
Fix this:
> - Erreur avec la traditionnelle différence entre Windows
-> et LInux sur les retour-chariots : le calcul de la
+> et Linux sur les retour-chariots : le calcul de la
> longueur du message ou des entêtes à envoyer au serveur
> cible n'est pas bon sur une machine Windows.
> Ci-dessous la modif :
>
> # No NL Count on Windows my $length = ( -s $file ) + $bare_nl_count;
> my $length = ( -s $file );
+I wonder if it is Windows or the imap server used.
Add stdin/stdout filter before transfer:
@@ -170,6 +165,17 @@ Explain expunge behavior.
===========================================================================
+DONE. Evaluate memory consumption with (or better):
+print qx{ ps o pid,pcpu,comm,vsz,rss,size $$ }, "\n"
+Search memory leaks with
+Test-Weaken Test-Memory-Cycle Devel-Cycle Devel-Leak Test-Weaken
+sh -x tests.sh ll_bigmail
+sh -x tests.sh ll_memory_consumption
+are good candidate to stress memory.
+No memory leak detected just up to 8 memory copies of the same data
+in Mail::IMAPClient.
+Wrote ./learn/memory_consumption to show that it is a Mail::IMAPClient issue.
+
DONE.Be more effiscient with large mailboxes
Write a Mail::IMAPClient::fetch_hash allowing selecting messages to fetch
4 hours estimated time coding. Time spent 4h30 (with public release and emails)
diff --git a/VERSION b/VERSION
index 0b73934..f014005 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.350
+1.366
diff --git a/VERSION_EXE b/VERSION_EXE
index bcdbccd..a31af01 100644
--- a/VERSION_EXE
+++ b/VERSION_EXE
@@ -1 +1 @@
-1.350
+1.366
diff --git a/build_exe.bat b/build_exe.bat
index f100900..2022c35 100755
--- a/build_exe.bat
+++ b/build_exe.bat
@@ -1,10 +1,10 @@
-REM $Id: build_exe.bat,v 1.3 2010/09/06 02:16:24 gilles Exp gilles $
+REM $Id: build_exe.bat,v 1.6 2010/10/24 23:51:48 gilles Exp gilles $
echo Building imapsync.exe
cd C:\msys\1.0\home\Admin\imapsync
perl -mMail::IMAPClient -mDigest::MD5 -mTerm::ReadKey -mIO::Socket::SSL -mFile::Spec -mDigest::HMAC_MD5 -e ''
-pp -o imapsync.exe -M Term::ReadKey -M IO::Socket::SSL -M Digest::HMAC_MD5 imapsync
+pp -o imapsync.exe -M Mail::IMAPClient -M IO::Socket -M IO::Socket::SSL -M Digest::MD5 -M Digest::HMAC_MD5 -M Term::ReadKey imapsync
echo Done building imapsync.exe
diff --git a/freshmeat_submition.inp b/freshmeat_submition.inp
index 4268236..4d8a193 100644
--- a/freshmeat_submition.inp
+++ b/freshmeat_submition.inp
@@ -3,8 +3,8 @@
#RELEASE_FOCUS="Initial freshmeat announcement"
#RELEASE_FOCUS="Documentation"
#RELEASE_FOCUS="Code cleanup"
-#RELEASE_FOCUS="Minor feature enhancements"
-RELEASE_FOCUS="Major feature enhancements"
+RELEASE_FOCUS="Minor feature enhancements"
+#RELEASE_FOCUS="Major feature enhancements"
#RELEASE_FOCUS="Minor bugfixes"
#RELEASE_FOCUS="Major bugfixes"
#RELEASE_FOCUS="Minor security fixes"
@@ -12,9 +12,4 @@ RELEASE_FOCUS="Major feature enhancements"
#TEXT_BODY="Syntax cleanup"
#TEXT_BODY="Updated documentation"
-TEXT_BODY="
-Since 1.350:
-Bug fixes.
-Many thanks to the freshmeat folk that correct my bad and poorly English!
-"
-
+TEXT_BODY="Since last public release 1.350: Added --minsize option to transfer messages bigger than a given size. Added memory consumption to know how much concurent imapsync can run in parallel on a system. Thanks to the freshmeat guy who corrects my bad and poorly English!"
diff --git a/freshmeat_submition.json b/freshmeat_submition.json
index 8b2c6e6..0088346 100644
--- a/freshmeat_submition.json
+++ b/freshmeat_submition.json
@@ -1,31 +1,9 @@
{
"release": {
- "tag_list": "Major feature enhancements",
- "version": "1.350",
+ "tag_list": "stable, Minor feature enhancements",
+ "version": "1.359",
"hidden_from_frontpage": false,
- "changelog": "
-Since 1.286:
-Added --noreleasecheck option.
-Added new release checking.
-Removed Date::Manip dependancy.
-Better output when copying messages.
-More effiscient with large mailboxes.
-Clarity: print capability after authenticated state.
-Duplicate messages on host2 are now deleted with --delete2
---skipsize turned on by default.
-Usability fix: examples with --password1 instead of --passfile1
-Added --debugimap1 --debugimap2 to permit imap outpout with only one host.
-Added reconnect statistics.
-Added reconnect behavior with Mail::IMAPClient 2.2.9
-Added statistic about messages deleted.
-Added statistic about average bandwith rate.
-Flags are now exactly synced from host1 to host2
-(Previous releases just added flags).
-Added TLSv1 support.
-Filter flags sync with the list given by PERMANENTFLAGS on --host2
-and bug fixes.
-Many thanks to the freshmeat folk that correct my bad and poorly English!
-"
+ "changelog": "Since last public release 1.350: Added --minsize option to transfer messages bigger than a given size.Added memory consumption to know how much concurent imapsync can run in parallel on a system. Many thanks to the freshmeat folk that correct my bad and poorly English!"
}
}
diff --git a/imapsync b/imapsync
index 37bcfe5..f20a9dd 100755
--- a/imapsync
+++ b/imapsync
@@ -20,7 +20,7 @@ tool. Synchronise mailboxes between two imap servers. Good
at IMAP migration. More than 36 different IMAP server softwares
supported with success.
-$Revision: 1.350 $
+$Revision: 1.366 $
=head1 SYNOPSIS
@@ -36,12 +36,14 @@ To synchronise imap account "foo" on "imap.truc.org"
=head1 INSTALL
imapsync works fine under any Unix OS with perl.
- imapsync works fine under Windows (2000, XP) with ActiveState's 5.8 Perl
- or as a standalone binary software.
+ imapsync works fine under Windows (2000, XP)
+ with Strawberry Perl 5.10 or 5.12
+ or as a standalone binary software imapsync.exe
imapsync is already available directly on the following distributions
(at least):
-FreeBSD, Debian, Ubuntu, Gentoo, NetBSD, Darwin, Mandriva and OpenBSD (yeah!).
+FreeBSD, Debian, Ubuntu, Gentoo, Fedora,
+NetBSD, Darwin, Mandriva and OpenBSD (yeah!).
Get imapsync at
http://www.linux-france.org/prj/imapsync/
@@ -91,6 +93,7 @@ The option list:
[--syncacls]
[--regexmess ] [--regexmess ]
[--maxsize ]
+ [--minsize ]
[--maxage ]
[--minage ]
[--skipheader ]
@@ -228,8 +231,9 @@ The public mailing-list may be the best way to get support.
To write on the mailing-list, the address is:
-To subscribe, send a message to:
+To subscribe, send any message (even empty) to:
+then just reply to the confirmation message.
To unsubscribe, send a message to:
@@ -471,7 +475,7 @@ Entries for imapsync:
Feedback (good or bad) will often be welcome.
-$Id: imapsync,v 1.350 2010/09/06 01:05:09 gilles Exp gilles $
+$Id: imapsync,v 1.366 2010/10/25 17:15:52 gilles Exp gilles $
=cut
@@ -489,7 +493,8 @@ use Digest::MD5 qw(md5_base64);
#use IO::Socket::SSL;
use MIME::Base64;
use English;
-use POSIX qw(uname);
+use File::Basename;
+use POSIX qw(uname SIGALRM);
use Fcntl;
use File::Spec;
use File::Path qw(mkpath rmtree);
@@ -500,6 +505,14 @@ use Test::More 'no_plan';
eval { require 'usr/include/sysexits.ph' };
+use constant {
+ Unconnected => 0,
+ Connected => 1, # connected; not logged in
+ Authenticated => 2, # logged in; no mailbox selected
+ Selected => 3, # mailbox selected
+};
+
+
# global variables
my(
@@ -516,7 +529,7 @@ my(
$usedatemanip,
$syncacls,
$fastio1, $fastio2,
- $maxsize, $maxage, $minage,
+ $maxsize, $minsize, $maxage, $minage,
$skipheader, @useheader,
$skipsize, $allowsizemismatch, $foldersizes, $buffersize,
$delete, $delete2,
@@ -554,13 +567,16 @@ my(
$allow3xx, $justlogin,
$tmpdir,
$releasecheck,
+ $max_msg_size_in_bytes,
+ $modules_version,
+ $delete2folders,
);
# main program
# global variables initialisation
-$rcs = '$Id: imapsync,v 1.350 2010/09/06 01:05:09 gilles Exp gilles $ ';
+$rcs = '$Id: imapsync,v 1.366 2010/10/25 17:15:52 gilles Exp gilles $ ';
$total_bytes_transferred = 0;
$total_bytes_skipped = 0;
@@ -573,6 +589,7 @@ $h1_nb_msg_noheader = $h2_nb_msg_noheader = 0;
$h1_total_bytes_duplicate = $h2_total_bytes_duplicate = 0;
$nb_errors = 0;
+$max_msg_size_in_bytes = 0;
unless(defined(&_SYSEXITS_H)) {
# 64 on my linux box.
@@ -584,6 +601,8 @@ my @argv_copy = @ARGV;
get_options();
+$modules_version = defined($modules_version) ? $modules_version : 1;
+
$releasecheck = defined($releasecheck) ? $releasecheck : 1;
my $warn_release = ($releasecheck) ? check_last_release() : '';
@@ -602,7 +621,8 @@ print "Temp directory is $tmpdir\n";
is_valid_directory($tmpdir);
write_pidfile($pidfile) if ($pidfile);
-print "Modules version list:\n", modules_VERSION(), "\n";
+$modules_version and print "Modules version list:\n", modules_VERSION(), "\n";
+
check_lib_version() or
die "imapsync needs perl lib Mail::IMAPClient release 2.2.9, or 3.0.25 or superior \n";
@@ -623,19 +643,6 @@ $debugimap1 = $debugimap2 = 1 if ($debugimap);
# By default, don't take size to compare
$skipsize = (defined $skipsize) ? $skipsize : 1;
-sub connect_imap {
- my($host, $port, $debugimap, $ssl, $tls) = @_;
- my $imap = Mail::IMAPClient->new();
- $imap->Server($host);
- $imap->Port($port);
- $imap->Debug($debugimap);
- $imap->Ssl($ssl) if ($ssl);
- $imap->Tls($tls) if ($tls);
- #$imap->connect()
- myconnect($imap)
- or die_clean("Can not open imap connection on [$host]: $@\n");
-}
-
if ($justconnect) {
justconnect();
@@ -693,19 +700,6 @@ $reconnectretry2 = (defined($reconnectretry2)) ? $reconnectretry2 : 3;
print "Host1: imap server [$host1] port [$port1] user [$user1]\n";
print "Host2: imap server [$host2] port [$port2] user [$user2]\n";
-
-sub ask_for_password {
- my ($user, $host) = @_;
- print "What's the password for $user\@$host? ";
- Term::ReadKey::ReadMode(2);
- my $password = <>;
- chomp $password;
- printf "\n";
- Term::ReadKey::ReadMode(0);
- return $password;
-}
-
-
$password1 || $passfile1 || do {
$password1 = ask_for_password($authuser1 || $user1, $host1);
};
@@ -740,6 +734,820 @@ $debug and print "Host1 Buffer I/O: ", $imap1->Buffer(), "\n";
$debug and print "Host2 Buffer I/O: ", $imap2->Buffer(), "\n";
+
+die_clean() unless $imap1->IsAuthenticated();
+print "Host1: state Authenticated\n";
+die_clean() unless $imap2->IsAuthenticated();
+print "Host2: state Authenticated\n";
+
+print "Host1 capability: ", join(" ", $imap1->capability_update()), "\n";
+print "Host2 capability: ", join(" ", $imap2->capability_update()), "\n";
+
+
+exit_clean(0) if ($justlogin);
+
+$split1 and $imap1->Split($split1);
+$split2 and $imap2->Split($split2);
+
+#
+# Folder stuff
+#
+
+my (
+@h1_folders_all, %h1_folders_all, @h1_folders_wanted, %requested_folder, %subscribed_folder,
+@h2_folders_all, %h2_folders_all, @h2_folders_from_1, %h2_folders_from_1,
+);
+
+
+# Make a hash of subscribed folders in source server.
+map { $subscribed_folder{$_} = 1 } $imap1->subscribed();
+
+# All folders on host1 and host2
+@h1_folders_all = sort $imap1->folders();
+@h2_folders_all = sort $imap2->folders();
+
+map { $h1_folders_all{$_} = 1} @h1_folders_all;
+map { $h2_folders_all{$_} = 1} @h2_folders_all;
+
+if (scalar(@folder) or $subscribed or scalar(@folderrec)) {
+ # folders given by option --folder
+ if (scalar(@folder)) {
+ add_to_requested_folders(@folder);
+ }
+
+ # option --subscribed
+ if ($subscribed) {
+ add_to_requested_folders(keys (%subscribed_folder));
+ }
+
+ # option --folderrec
+ if (scalar(@folderrec)) {
+ foreach my $folderrec (@folderrec) {
+ add_to_requested_folders($imap1->folders($folderrec));
+ }
+ }
+}
+else {
+ # no include, no folder/subscribed/folderrec options => all folders
+ if (not scalar(@include)) {
+ add_to_requested_folders(@h1_folders_all);
+ }
+}
+
+
+# consider (optional) includes and excludes
+if (scalar(@include)) {
+ foreach my $include (@include) {
+ my @included_folders = grep /$include/, @h1_folders_all;
+ add_to_requested_folders(@included_folders);
+ print "Including folders matching pattern '$include': @included_folders\n";
+ }
+}
+
+if (scalar(@exclude)) {
+ foreach my $exclude (@exclude) {
+ my @requested_folder = sort(keys(%requested_folder));
+ my @excluded_folders = grep /$exclude/, @requested_folder;
+ remove_from_requested_folders(@excluded_folders);
+ print "Excluding folders matching pattern '$exclude': @excluded_folders\n";
+ }
+}
+
+# Remove no selectable folders
+
+foreach my $folder (keys(%requested_folder)) {
+ if ( not $imap1->selectable($folder)) {
+ print "Warning: ignoring folder $folder because it is not selectable\n";
+ remove_from_requested_folders($folder);
+ }
+}
+
+
+my @requested_folder = sort(keys(%requested_folder));
+
+@h1_folders_wanted = @requested_folder;
+
+my($h1_sep,$h2_sep);
+# what are the private folders separators for each server ?
+
+$debug and print "Getting separators\n";
+$h1_sep = get_separator($imap1, $sep1, "--sep1");
+$h2_sep = get_separator($imap2, $sep2, "--sep2");
+
+#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($h1_prefix,$h2_prefix);
+$h1_prefix = get_prefix($imap1, $prefix1, "--prefix1");
+$h2_prefix = get_prefix($imap2, $prefix2, "--prefix2");
+
+
+print "Host1 separator and prefix: [$h1_sep][$h1_prefix]\n";
+print "Host2 separator and prefix: [$h2_sep][$h2_prefix]\n";
+
+
+foreach my $h1_fold (@h1_folders_wanted) {
+ my $h2_fold;
+ $h2_fold = imap2_folder_name($h1_fold);
+ $h2_folders_from_1{$h2_fold}++;
+}
+
+@h2_folders_from_1 = sort keys(%h2_folders_from_1);
+
+if ($foldersizes) {
+ foldersizes("Host1", $imap1, @h1_folders_wanted);
+ foldersizes("Host2", $imap2, @h2_folders_from_1);
+}
+
+
+exit_clean(0) if ($justfoldersizes);
+
+print
+ "++++ Listing folders\n",
+ "Host1 folders list:\n", map("[$_]\n",@h1_folders_all),"\n",
+ "Host2 folders list:\n", map("[$_]\n",@h2_folders_all),"\n";
+
+print
+ "Host1 subscribed folders list: ",
+ map("[$_] ", sort keys(%subscribed_folder)), "\n"
+ if ($subscribed);
+
+my @h2_folders_not_in_1;
+@h2_folders_not_in_1 = list_folders_in_2_not_in_1();
+
+print "Folders in host2 not in host1:\n",
+ map("[$_]\n", @h2_folders_not_in_1),"\n";
+
+delete_folders_in_2_not_in_1() if $delete2folders;
+
+# folder loop
+print "++++ Looping on each folder\n";
+
+FOLDER: foreach my $h1_fold (@h1_folders_wanted) {
+
+ last FOLDER if $imap1->IsUnconnected();
+ last FOLDER if $imap2->IsUnconnected();
+
+ my $h2_fold = imap2_folder_name($h1_fold);
+
+ printf("%-35s -> %-35s\n", "[$h1_fold]", "[$h2_fold]");
+ unless ($imap1->select($h1_fold)) {
+ warn
+ "Host1 folder $h1_fold: Could not select: ",
+ $imap1->LastError, "\n";
+ $nb_errors++;
+ next FOLDER;
+ }
+
+ if ( ! exists($h2_folders_all{$h2_fold})) {
+ print "Host2 folder $h2_fold does not exist\n";
+ print "Creating folder [$h2_fold]\n";
+ unless ($dry){
+ unless ($imap2->create($h2_fold)){
+ warn "Couldn't create [$h2_fold]: ",
+ $imap2->LastError,"\n";
+ $nb_errors++;
+ next FOLDER;
+ }
+ }
+ else{
+ next FOLDER;
+ }
+ }
+
+ acls_sync($h1_fold, $h2_fold);
+
+ unless ($imap2->select($h2_fold)) {
+ warn
+ "Host2 folder $h2_fold: Could not select: ",
+ $imap2->LastError, "\n";
+ $nb_errors++;
+ next FOLDER;
+ }
+ my @select_results = $imap2->Results();
+
+ #print "%%% @select_results\n";
+ my $permanentflags2 = permanentflags(@select_results);
+
+ if ($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{$h1_fold}) or $subscribe_all) {
+ print "Subscribing to folder $h2_fold on destination server\n";
+ unless($dry) { $imap2->subscribe($h2_fold) };
+ }
+
+ next FOLDER if ($justfolders);
+
+ last FOLDER if $imap1->IsUnconnected();
+ last FOLDER if $imap2->IsUnconnected();
+
+ my @h1_msgs = select_msgs($imap1);
+
+ $debug and print "LIST Host1: ", scalar(@h1_msgs), " messages [@h1_msgs]\n";
+ # internal dates on host2 are after the ones on host1
+ # normally...
+ my @h2_msgs = select_msgs($imap2);
+
+ $debug and print "LIST Host2: ", scalar(@h2_msgs), " messages [@h2_msgs]\n";
+
+ my %h1_hash = ();
+ my %h2_hash = ();
+
+ $debug and print "Host1 folder [$h1_fold] parsing headers\n";
+ last FOLDER if $imap1->IsUnconnected();
+ last FOLDER if $imap2->IsUnconnected();
+
+ my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
+ $h1_heads_ref = $imap1->parse_headers([@h1_msgs], @useheader) if (@h1_msgs);
+ $debug and print "Time headers: ", timenext(), " s\n";
+ last FOLDER if $imap1->IsUnconnected();
+
+ @$h1_fir_ref{@h1_msgs} = (undef);
+ $h1_fir_ref = $imap1->fetch_hash_2("FLAGS", "INTERNALDATE", "RFC822.SIZE", $h1_fir_ref)
+ if (@h1_msgs);
+ $debug and print "Time fir: ", timenext(), " s\n";
+ unless ($h1_fir_ref) {
+ warn
+ "Host1 folder $h1_fold: Could not fetch_hash_2 ",
+ scalar(@h1_msgs), " msgs: ", $imap1->LastError, "\n";
+ $nb_errors++;
+ next FOLDER;
+ }
+ last FOLDER if $imap1->IsUnconnected();
+
+
+ my @h1_msgs_duplicate;
+ foreach my $m (@h1_msgs) {
+ my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, "F", \%h1_hash);
+ if (! defined($rc)) {
+ my $h1_size = $h1_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
+ print "+ Skipping msg #$m:$h1_size on host1 folder $h1_fold (no header so we ignore this message)\n";
+ $total_bytes_skipped += $h1_size;
+ $nb_msg_skipped += 1;
+ $h1_nb_msg_noheader +=1;
+ } elsif(0 == $rc) {
+ # duplicate
+ push(@h1_msgs_duplicate, $m);
+ # duplicate, same id same size?
+ my $h1_size = $h1_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
+ $nb_msg_skipped += 1;
+ $h1_total_bytes_duplicate += $h1_size;
+ $h1_nb_msg_duplicate += 1;
+ }
+ }
+ $debug and print "Time parsing headers on host1: ", timenext(), " s\n";
+
+ $debug and print "Host2 folder [$h2_fold] parsing headers\n";
+
+ my ($h2_heads_ref, $h2_fir_ref) = ({}, {});
+ $h2_heads_ref = $imap2->parse_headers([@h2_msgs], @useheader) if (@h2_msgs);
+ $debug and print "Time headers: ", timenext(), " s\n";
+ last FOLDER if $imap2->IsUnconnected();
+
+ @$h2_fir_ref{@h2_msgs} = (undef); # fetch_hash_2 can select by uid with last arg as ref
+ $h2_fir_ref = $imap2->fetch_hash_2("FLAGS", "INTERNALDATE", "RFC822.SIZE", $h2_fir_ref)
+ if (@h2_msgs);
+ $debug and print "Time fir: ", timenext(), " s\n";
+ last FOLDER if $imap2->IsUnconnected();
+
+ my @h2_msgs_duplicate;
+ foreach my $m (@h2_msgs) {
+ my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, "T", \%h2_hash);
+ if (! defined($rc)) {
+ my $h2_size = $h2_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
+ print "+ Skipping msg #$m:$h2_size in host2 folder $h2_fold (no header so we ignore this message)\n";
+ $h2_nb_msg_noheader += 1 ;
+ } elsif(0 == $rc) {
+ # duplicate
+ my $h2_size = $h2_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
+ $h2_nb_msg_duplicate += 1;
+ $h2_total_bytes_duplicate += $h2_size;
+ push(@h2_msgs_duplicate, $m);
+ }
+ }
+ $debug and print "Time parsing headers on host2: ", timenext(), " s\n";
+
+ $debug and print "++++ Verifying [$h1_fold] -> [$h2_fold]\n";
+ # messages in host1 that are not in host2
+
+ my @h1_hash_keys_sorted_by_uid
+ = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys(%h1_hash);
+
+ #print map { $h1_hash{$_}{'m'} . " "} @h1_hash_keys_sorted_by_uid;
+
+ my @h2_hash_keys_sorted_by_uid
+ = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys(%h2_hash);
+
+
+ if($delete2) {
+ my @h2_expunge;
+ foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
+ #print "$m_id ";
+ unless (exists($h1_hash{$m_id})) {
+ my $h2_msg = $h2_hash{$m_id}{'m'};
+ my $h2_flags = $h2_hash{$m_id}{'F'} || "";
+ my $isdel = $h2_flags =~ /\B\\Deleted\b/ ? 1 : 0;
+ print "msg $h2_fold/$h2_msg deleted on host2 [$m_id]\n"
+ if ! $isdel;
+ push(@h2_expunge, $h2_msg) if $uidexpunge2;
+ unless ($dry or $isdel) {
+ $imap2->delete_message($h2_msg);
+ $h2_nb_msg_deleted += 1;
+ }
+ }
+ }
+ foreach my $h2_msg (@h2_msgs_duplicate) {
+ print "msg $h2_fold/$h2_msg deleted [duplicate] on host2\n";
+ push(@h2_expunge, $h2_msg) if $uidexpunge2;
+ unless ($dry) {
+ $imap2->delete_message($h2_msg);
+ $h2_nb_msg_deleted += 1;
+ }
+ }
+
+ my $cnt = scalar @h2_expunge;
+ if(@h2_expunge and !$imap2->can("uidexpunge")) {
+ warn "uidexpunge not supported (< IMAPClient 3.17)\n";
+ }
+ elsif(@h2_expunge) {
+ print "uidexpunge $cnt message(s)\n";
+ $imap2->uidexpunge(\@h2_expunge) if !$dry;
+ }
+ }
+
+ 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 $h1_size >= $maxsize) {
+ print "msg $h1_fold/$h1_msg skipping ($h1_size exceeds maxsize limit $maxsize bytes)\n";
+ $total_bytes_skipped += $h1_size;
+ $nb_msg_skipped += 1;
+ next MESS;
+ }
+ if (defined $minsize and $h1_size <= $minsize) {
+ print "msg $h1_fold/$h1_msg skipping ($h1_size smaller than minsize $minsize bytes)\n";
+ $total_bytes_skipped += $h1_size;
+ $nb_msg_skipped += 1;
+ next MESS;
+ }
+
+ unless (exists($h2_hash{$m_id})) {
+ # copy
+ $debug and print "msg $h1_fold/$h1_msg copying to $h2_fold\n";
+ last FOLDER if $imap1->IsUnconnected();
+ last FOLDER if $imap2->IsUnconnected();
+ my $string;
+ #print "Message_string Beg\n", memory_consumption();
+ $string = $imap1->message_string($h1_msg);
+ #print "Message_string End\n", memory_consumption();
+ unless (defined($string)) {
+ warn
+ "- msg $h1_fold/$h1_msg could not fetch [$m_id $h1_size]: ",
+ $imap1->LastError, "\n";
+ $nb_errors++;
+ $total_bytes_error += $h1_size;
+ next MESS;
+ }
+
+ #my $message_file = "tmp_imapsync_$$";
+ #$imap1->select($h1_fold);
+ #unlink($message_file);
+ #$imap1->message_to_file($message_file, $h1_msg) or do {
+ # warn "Could not put message #$h1_msg to file $message_file",
+ # $imap1->LastError;
+ # $nb_errors++;
+ # $total_bytes_error += $h1_size;
+ # next MESS;
+ #};
+ #$string = file_to_string($message_file);
+ #print "AAA1[$string]ZZZ\n";
+ #unlink($message_file);
+ if (@regexmess) {
+ $string = regexmess($string);
+
+ #string_to_file($string, $message_file);
+ }
+
+
+ $debug and print
+ "=" x80, "\n",
+ "F message content begin next line\n",
+ $string,
+ "F message content ended on previous line\n", "=" x 80, "\n";
+ my $h1_date = "";
+ if ($syncinternaldates) {
+ $h1_date = $h1_idate;
+ $debug and print "internal date from host1: [$h1_date]\n";
+ $h1_date = good_date($h1_date);
+ $debug and print "internal date from host1: [$h1_date] (fixed)\n";
+ }
+
+ if ($idatefromheader) {
+
+ $h1_date = $imap1->get_header($h1_msg,"Date");
+ $debug and print "header date from host1: [$h1_date]\n";
+ $h1_date = good_date($h1_date);
+ $debug and print "header date from host1: [$h1_date] (fixed)\n";
+ }
+
+ my $h1_flags = $h1_hash{$m_id}{'F'} || "";
+ # RFC 2060: This flag can not be altered by any client
+ $h1_flags =~ s@\\Recent\s?@@gi;
+ $h1_flags = flags_regex($h1_flags) if @regexflag;
+
+ $h1_flags = flags_filter($h1_flags, $permanentflags2) if ($permanentflags2);
+
+ my $new_id;
+ $debug and print "msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n";
+ last FOLDER if $imap1->IsUnconnected();
+ last FOLDER if $imap2->IsUnconnected();
+ $h1_date = undef if ($h1_date eq "");
+
+ unless ($dry) {
+ $max_msg_size_in_bytes = max($h1_size, $max_msg_size_in_bytes);
+ $new_id = $imap2->append_string($h2_fold, $string, $h1_flags, $h1_date);
+ unless($new_id){
+ no warnings 'uninitialized';
+ warn "- msg $h1_fold/$h1_msg couldn't append (Subject:[".
+ $imap1->subject($h1_msg)."]) to folder $h2_fold: ",
+ $imap2->LastError, "\n";
+ $nb_errors++;
+ $total_bytes_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 "msg $h1_fold/$h1_msg copied to $h2_fold/$new_id\n";
+ $total_bytes_transferred += $h1_size;
+ $nb_msg_transferred += 1;
+ if($delete) {
+ print "msg $h1_fold/$h1_msg deleted on host1\n";
+ unless($dry) {
+ $imap1->delete_message($h1_msg);
+ $h1_nb_msg_deleted += 1;
+ last FOLDER if $imap1->IsUnconnected();
+ $imap1->expunge() if ($expunge);
+ last FOLDER if $imap1->IsUnconnected();
+ }
+ }
+ }
+ }
+ else{
+ $nb_msg_skipped_dry_mode += 1;
+ }
+ #unlink($message_file);
+ next MESS;
+ }
+ else{
+ #my $h2_size = $h2_hash{$m_id}{'s'};
+ my $h2_msg = $h2_hash{$m_id}{'m'};
+ #my $h2_idate = $h2_hash{$m_id}{'D'};
+ $debug and print "msg $h1_fold/$h1_msg equals $h2_fold/$h2_msg\n";
+ $total_bytes_skipped += $h1_size;
+ $nb_msg_skipped += 1;
+ }
+
+ $fast and next MESS;
+ #$debug and print "MESSAGE $m_id\n";
+ my $h2_size = $h2_hash{$m_id}{'s'};
+ my $h2_msg = $h2_hash{$m_id}{'m'};
+
+ # used cached flag values for efficiency
+ my $h1_flags = $h1_hash{$m_id}{'F'} || "";
+ my $h2_flags = $h2_hash{$m_id}{'F'} || "";
+
+ # RFC 2060: This flag can not be altered by any client
+ $h1_flags =~ s@\\Recent\s?@@gi;
+ $h1_flags = flags_regex($h1_flags) if @regexflag;
+ $h1_flags = flags_filter($h1_flags, $permanentflags2) if ($permanentflags2);
+
+
+ # compare flags - set flags if there a difference
+ my @h1_flags = sort split(' ', $h1_flags );
+ my @h2_flags = sort split(' ', $h2_flags );
+ my $diff = compare_lists(\@h1_flags, \@h2_flags);
+
+ $diff and $debug and print "msg $h2_fold/$h2_msg replacing h2 flags($h2_flags) with h1 flags($h1_flags)\n";
+
+ # This sets flags so flags can be removed with this
+ # When you remove a \Seen flag on host1 you want to it
+ # to be removed on host2. Just add flags is not what
+ # we need most of the time.
+
+ if (!$dry and $diff and !$imap2->store($h2_msg, "FLAGS.SILENT (@h1_flags)") ) {
+ warn "- msg $h2_fold/$h2_msg could not add flags @h1_flags",
+ $imap2->LastError, "\n";
+ #$nb_errors++;
+ }
+ last FOLDER if $imap2->IsUnconnected();
+
+ $debug and do {
+ my @h2_flags = @{ $imap2->flags($h2_msg) || [] };
+ last FOLDER if $imap2->IsUnconnected();
+
+ print "host1 flags: $h1_flags\n",
+ "host2 flags: @h2_flags\n";
+
+ print "Looking dates\n";
+ #my $h1_idate = $imap1->internaldate($h1_msg);
+ #my $h2_idate = $imap2->internaldate($h2_msg);
+ my $h1_idate = $h1_hash{$m_id}{'D'};
+ my $h2_idate = $h2_hash{$m_id}{'D'};
+ print
+ "host1 internal date: $h1_idate\n",
+ "host2 internal date: $h2_idate\n";
+
+ #unless ($h1_idate eq $h2_idate) {
+ # print "!!! Dates differs !!!\n";
+ #}
+ };
+ unless ($skipsize or ($h1_size == $h2_size)) {
+ # Bad size
+ print
+ "- msg $h1_fold/$h1_msg size diff $h1_size != $h2_size $h2_fold/$h2_msg\n";
+ $nb_errors++;
+ }
+ else {
+ # Good
+ $debug and print
+ "msg $h1_fold/$h1_msg sizes ok $h1_size <=> $h2_size $h2_fold/$h2_msg\n";
+ if($delete) {
+ print "msg $h1_fold/$h1_msg deleted on host1\n";
+ unless($dry) {
+ $imap1->delete_message($h1_msg);
+ $h1_nb_msg_deleted += 1;
+ $imap1->expunge() if ($expunge);
+ }
+ }
+ }
+ }
+ if ($expunge1){
+ print "Expunging host1 folder $h1_fold\n";
+ unless($dry) { $imap1->expunge() };
+ }
+ if ($expunge2){
+ print "Expunging host2 folder $h2_fold\n";
+ unless($dry) { $imap2->expunge() };
+ }
+
+$debug and print "Time: ", timenext(), " s\n";
+}
+
+print "++++ End looping on each folder\n";
+#print memory_consumption();
+
+my $memory_consumption = memory_consumption();
+my $memory_ratio = ($max_msg_size_in_bytes) ?
+ sprintf('%.1f', $memory_consumption / $max_msg_size_in_bytes) : "NA";
+
+
+$imap1->logout();
+$imap2->logout();
+
+my $host1_reconnect_count = $imap1->Reconnect_counter() || 0;
+my $host2_reconnect_count = $imap2->Reconnect_counter() || 0;
+
+
+$timeend = time();
+$timediff = $timeend - $timestart;
+
+stats();
+exit_clean(1) if($nb_errors);
+exit_clean(0);
+
+# END of main program
+
+# subroutines
+
+sub max {
+ return(undef) if (0 == scalar(@_));
+ my @sorted = sort { $a <=> $b } @_;
+ return(pop(@sorted));
+}
+
+sub tests_max {
+ ok(0 == max(0), "max 0");
+ ok(1 == max(1), "max 1");
+ ok(-1 == max(-1), "max -1");
+ ok(! defined(max()), "max no arg");
+ ok(100 == max(1, 100), "max 1 100");
+ ok(100 == max(100, 1), "max 100 1");
+ ok(100 == max(100, 42, 1), "max 100 42 1");
+ ok(100 == max(100, "42", 1), "max 100 42 1");
+ ok(100 == max("100", "42", 1), "max 100 42 1");
+ #ok(100 == max(100, "haha", 1), "max 100 42 1");
+}
+
+sub check_lib_version {
+ $debug and print "IMAPClient $Mail::IMAPClient::VERSION\n";
+ if ($Mail::IMAPClient::VERSION eq '2.2.9') {
+ override_imapclient();
+ return(1);
+ }
+ else{
+ # 3.x.x is no longer buggy with imapsync.
+ if ($allow3xx) {
+ return(1);
+ }else{
+ return(0);
+ }
+ }
+}
+
+sub modules_VERSION {
+
+ my @list_version;
+
+ foreach my $module (qw(
+Mail::IMAPClient
+IO::Socket
+IO::Socket::SSL
+Digest::MD5
+Digest::HMAC_MD5
+Term::ReadKey))
+ {
+ my $v = "?";
+
+ if (eval "require $module") {
+ # module is here
+ $v = eval "\$${module}::VERSION";
+ }else{
+ # no module
+ $v = "?";
+ }
+ #print ("$module ", $v, "\n");
+ push (@list_version, sprintf("%-20s %s\n", $module, $v));
+ }
+ return(@list_version);
+}
+
+# Construct a command line copy with passwords replaced by MASKED.
+sub command_line_nopassword {
+ my @argv_copy = @_;
+ my @argv_nopassword;
+ while (@argv_copy) {
+ my $arg = shift(@argv_copy); # option name or value
+ if ($arg =~ m/-password[12]/) {
+ shift(@argv_copy); # password value
+ push(@argv_nopassword, $arg, "MASKED"); # option name and fake value
+ }else{
+ push(@argv_nopassword, $arg); # same option or value
+ }
+ }
+ return("@argv_nopassword");
+}
+
+sub tests_command_line_nopassword {
+
+ ok('' eq command_line_nopassword(), 'command_line_nopassword void');
+ ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
+ #print command_line_nopassword((qw{ --password1 secret1 })), "\n";
+ ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
+ ok('--blabla --password1 MASKED --blibli'
+ eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
+
+
+}
+
+sub ask_for_password {
+ my ($user, $host) = @_;
+ print "What's the password for $user\@$host? ";
+ Term::ReadKey::ReadMode(2);
+ my $password = <>;
+ chomp $password;
+ printf "\n";
+ Term::ReadKey::ReadMode(0);
+ return $password;
+}
+
+
+sub myconnect {
+ my $self = shift;
+
+ $debug and print "Entering myconnect\n";
+ %$self = (%$self, @_);
+
+ my $sock = (($self->Ssl) ? IO::Socket::SSL->new : IO::Socket::INET->new);
+ my $dp = ($self->Ssl ? 'imaps(993)' : 'imap(143)');
+
+ $debug and print "Calling configure\n";
+ my $ret = $sock->configure({
+ PeerAddr => $self->Server ,
+ PeerPort => $self->Port||$dp ,
+ Proto => 'tcp' ,
+ Timeout => $self->Timeout||0 ,
+ Debug => $self->Debug ,
+ });
+ unless ( defined($ret) ) {
+ $self->LastError( "$@\n");
+ $@ = "$@";
+ carp "$@"
+ unless defined wantarray;
+ return undef;
+ }
+ $sock->autoflush(1);
+
+ my $banner = $sock->getline();
+ $debug and print "Read: $banner";
+
+ $self->Banner($banner);
+ $self->RawSocket2($sock);
+ $self->State(Connected);
+
+ if ($self->Tls) {
+ starttls($self);
+ }
+
+ $self->Ignoresizeerrors($allowsizemismatch);
+
+ if ($self->User and $self->Password) {
+ $debug and print "Calling login\n";
+ return $self->login ;
+ }
+ else {
+ return $self;
+ }
+}
+
+
+
+
+sub starttls {
+ my $self = shift;
+ my $socket = $self->RawSocket2();
+
+ $debug and print "Entering starttls\n";
+ unless ($self->has_capability("STARTTLS")) {
+ die_clean( "No STARTTLS capability" );
+ }
+ print $socket, "\n";
+ print $socket "z00 STARTTLS\015\012";
+ CORE::select( undef, undef, undef, 0.025 );
+ my $txt = $socket->getline();
+ $debug and print "Read tls: $txt";
+ unless($txt =~ /^z00 OK/){
+ die_clean( "Invalid response for STARTTLS: $txt\n" );
+ }
+ $debug and print "Calling start_SSL\n";
+ unless(IO::Socket::SSL->start_SSL($socket,
+ {
+ SSL_version => "TLSV1",
+ SSL_startHandshake => 1,
+ SSL_verify_depth => 1,
+ }))
+ {
+ die_clean( "Couldn't start TLS: ".IO::Socket::SSL::errstr()."\n");
+ }
+ if (ref($socket) ne "IO::Socket::SSL") {
+ die_clean( "Socket has NOT been converted to SSL");
+ }else{
+ $debug and print "Socket successfuly converted to SSL\n";
+ }
+ $debug and print "Ending starttls\n";
+}
+
+
+
+sub connect_imap {
+ my($host, $port, $debugimap, $ssl, $tls) = @_;
+ my $imap = Mail::IMAPClient->new();
+ $imap->Ssl($ssl) if ($ssl);
+ $imap->Tls($tls) if ($tls);
+ $imap->Server($host);
+ $imap->Port($port);
+ $imap->Debug($debugimap);
+ #$imap->connect()
+ myconnect($imap)
+ or die_clean("Can not open imap connection on [$host]: $@\n");
+}
+
+sub justconnect {
+ my $imap1 = ();
+ my $imap2 = ();
+
+ $imap1 = connect_imap($host1, $port1, $debugimap1, $ssl1, $tls1);
+ print "Host1 software: ", server_banner($imap1);
+ print "Host1 capability: ", join(" ", $imap1->capability()), "\n";
+ $imap2 = connect_imap($host2, $port2, $debugimap2, $ssl2, $tls2);
+ print "Host2 software: ", server_banner($imap2);
+ print "Host2 capability: ", join(" ", $imap2->capability()), "\n";
+ $imap1->logout();
+ $imap2->logout();
+
+}
+
+
sub login_imap {
my($host, $port, $user, $password,
$debugimap, $timeout, $fastio,
@@ -750,7 +1558,7 @@ sub login_imap {
$imap->Ssl($ssl) if ($ssl);
$imap->Tls($tls) if ($tls);
- $imap->Clear(5);
+ $imap->Clear(1);
$imap->Server($host);
$imap->Port($port);
$imap->Fast_io($fastio);
@@ -806,6 +1614,7 @@ sub login_imap {
return($imap);
}
+
sub plainauth() {
my $code = shift;
my $imap = shift;
@@ -822,29 +1631,70 @@ sub server_banner {
return $banner;
}
-die_clean() unless $imap1->IsAuthenticated();
-print "Host1: state Authenticated\n";
-die_clean() unless $imap2->IsAuthenticated();
-print "Host2: state Authenticated\n";
-print "Host1 capability: ", join(" ", $imap1->capability_update()), "\n";
-print "Host2 capability: ", join(" ", $imap2->capability_update()), "\n";
+sub banner_imapsync {
+
+ my @argv_copy = @_;
+ my $banner_imapsync = join("",
+ '$RCSfile: imapsync,v $ ',
+ '$Revision: 1.366 $ ',
+ '$Date: 2010/10/25 17:15:52 $ ',
+ "\n",localhost_info(), "\n",
+ "Command line used:\n",
+ "$0 ", command_line_nopassword(@argv_copy), "\n",
+ );
+}
+
+sub is_valid_directory {
+ my $dir = shift;
+ return(1) if (-d $dir and -r _ and -w _);
+ # Trying to create it
+ mkpath($dir) or die "Error creating tmpdir $tmpdir : $!";
+ die "Error with tmpdir $tmpdir : $!" if not (-d $dir and -r _ and -w _);
+ return(1);
+}
-exit_clean(0) if ($justlogin);
-$split1 and $imap1->Split($split1);
-$split2 and $imap2->Split($split2);
-#
-# Folder stuff
-#
+sub write_pidfile {
+ my $pidfile = shift;
+
+ print "PID file is $pidfile\n";
+ if (-e $pidfile) {
+ warn "$pidfile already exists, overwriting it\n";
+ }
+ open(PIDFILE, ">$pidfile") or do {
+ warn "Could not open $pidfile for writing";
+ return undef;
+ };
+
+ print PIDFILE $PROCESS_ID;
+ close PIDFILE;
+ return($PROCESS_ID);
+}
+
+sub exit_clean {
+ my $status = shift;
+
+ unlink($pidfile);
+ exit($status);
+}
+
+sub die_clean {
+
+ unlink($pidfile);
+ die @_;
+}
+
+sub missing_option {
+ my ($option) = @_;
+ die_clean("$option option must be used, run $0 --help for help\n");
+}
+
-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" );
ok( !is_requested_folder('folder_foo') );
ok( add_to_requested_folders('folder_foo') );
ok( is_requested_folder('folder_foo') );
@@ -857,20 +1707,8 @@ sub tests_folder_routines {
ok( is_requested_folder('folder_toto') );
ok( remove_from_requested_folders('folder_toto') );
ok( !is_requested_folder('folder_toto') );
- ok( init_requested_folders() , 'empty requested folders');
- ok( !give_requested_folders() , 'no requested folders' );
}
-sub give_requested_folders {
- return(keys(%requested_folder));
-}
-
-sub init_requested_folders {
-
- %requested_folder = ();
- return(1);
-
-}
sub is_requested_folder {
my ( $folder ) = @_;
@@ -897,74 +1735,6 @@ sub remove_from_requested_folders {
return( keys(%requested_folder) );
}
-
-# Make a hash of subscribed folders in source server.
-map { $subscribed_folder{$_} = 1 } $imap1->subscribed();
-
-
-
-
-if (scalar(@folder) or $subscribed or scalar(@folderrec)) {
- # folders given by option --folder
- if (scalar(@folder)) {
- add_to_requested_folders(@folder);
- }
-
- # option --subscribed
- if ($subscribed) {
- add_to_requested_folders(keys (%subscribed_folder));
- }
-
- # option --folderrec
- if (scalar(@folderrec)) {
- foreach my $folderrec (@folderrec) {
- add_to_requested_folders($imap1->folders($folderrec));
- }
- }
-}
-else {
-
- # no include, no folder/subscribed/folderrec options => all folders
- if (not scalar(@include)) {
- my @all_source_folders = sort $imap1->folders();
- add_to_requested_folders(@all_source_folders);
- }
-}
-
-
-# consider (optional) includes and excludes
-if (scalar(@include)) {
- 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);
- print "Including folders matching pattern '$include': @included_folders\n";
- }
-}
-
-if (scalar(@exclude)) {
- foreach my $exclude (@exclude) {
- my @requested_folder = sort(keys(%requested_folder));
- my @excluded_folders = grep /$exclude/, @requested_folder;
- remove_from_requested_folders(@excluded_folders);
- print "Excluding folders matching pattern '$exclude': @excluded_folders\n";
- }
-}
-
-# Remove no selectable folders
-
-foreach my $folder (keys(%requested_folder)) {
- if ( not $imap1->selectable($folder)) {
- print "Warning: ignoring folder $folder because it is not selectable\n";
- remove_from_requested_folders($folder);
- }
-}
-
-
-my @requested_folder = sort(keys(%requested_folder));
-
-@h1_folders = @requested_folder;
-
sub compare_lists {
my ($list_1_ref, $list_2_ref) = @_;
@@ -1029,6 +1799,7 @@ sub tests_compare_lists {
ok( 0 == compare_lists([1,2], [1,2]) , "compare_lists, [1,2] = [1,2]") ;
ok(-1 == compare_lists([1], [1,2]) , "compare_lists, [1] < [1,2]") ;
+ ok(+1 == compare_lists([2], [1,2]) , "compare_lists, [2] > [1,2]") ;
ok(-1 == compare_lists([1], [1,1]) , "compare_lists, [1] < [1,1]") ;
ok(+1 == compare_lists([1, 1], [1]) , "compare_lists, [1, 1] > [1]") ;
ok( 0 == compare_lists([1 .. 20_000] , [1 .. 20_000])
@@ -1048,21 +1819,6 @@ sub tests_compare_lists {
}
-my($h1_sep,$h2_sep);
-# what are the private folders separators for each server ?
-
-$debug and print "Getting separators\n";
-$h1_sep = get_separator($imap1, $sep1, "--sep1");
-$h2_sep = get_separator($imap2, $sep2, "--sep2");
-
-#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($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) = @_;
@@ -1118,99 +1874,6 @@ sub get_separator {
}
}
-
-print "Host1 separator and prefix: [$h1_sep][$h1_prefix]\n";
-print "Host2 separator and prefix: [$h2_sep][$h2_prefix]\n";
-
-
-sub foldersizes {
-
- my ($side, $imap, $folders_r) = @_;
- my $tot = 0;
- my $tmess = 0;
- my @folders = @{$folders_r};
- print "++++ Calculating sizes\n";
- foreach my $folder (@folders) {
- my $stot = 0;
- my $smess = 0;
- printf("$side folder %-35s", "[$folder]");
- unless($imap->exists($folder)) {
- print("does not exist yet\n");
- next;
- }
- unless ($imap->examine($folder)) {
- warn
- "$side Folder $folder: Could not examine: ",
- $imap->LastError, "\n";
- $nb_errors++;
- next;
- }
-
- my $hash_ref = {};
- my @msgs = select_msgs($imap);
- $smess = scalar(@msgs);
- @$hash_ref{@msgs} = (undef);
- unless ($smess == 0) {
- $imap->fetch_hash_2("RFC822.SIZE",$hash_ref) or die_clean("$@");
- #print map {$hash_ref->{$_}->{"RFC822.SIZE"}, " "} keys %$hash_ref;
- map {$stot += $hash_ref->{$_}->{"RFC822.SIZE"}} keys %$hash_ref;
- }
-
- printf(" Size: %9s", $stot);
- printf(" Messages: %5s\n", $smess);
- $tot += $stot;
- $tmess += $smess;
- }
- print "Total size: $tot\n";
- print "Total messages: $tmess\n";
- print "Time: ", timenext(), " s\n";
-}
-
-
-foreach my $h1_fold (@h1_folders) {
- my $h2_fold;
- $h2_fold = imap2_folder_name($h1_fold);
- $h2_folders{$h2_fold}++;
-}
-
-@h2_folders = sort keys(%h2_folders);
-
-if ($foldersizes) {
- foldersizes("Host1", $imap1, \@h1_folders);
- foldersizes("Host2", $imap2, \@h2_folders);
-}
-
-
-sub timenext {
- my ($timenow, $timerel);
- # $timebefore is global, beurk !
- $timenow = time;
- $timerel = $timenow - $timebefore;
- $timebefore = $timenow;
- return($timerel);
-}
-
-exit_clean(0) if ($justfoldersizes);
-
-# needed for setting flags
-my $imap2hasuidplus = $imap2->has_capability("UIDPLUS");
-
-
-@h2_folders_list = sort @{$imap2->folders()};
-foreach my $folder (@h2_folders_list) {
- $h2_folders_list{$folder}++;
-}
-
-print
- "++++ Listing folders\n",
- "Host1 folders list:\n", map("[$_]\n",@h1_folders),"\n",
- "Host2 folders list:\n", map("[$_]\n",@h2_folders_list),"\n";
-
-print
- "Host1 subscribed folders list: ",
- map("[$_] ", sort keys(%subscribed_folder)), "\n"
- if ($subscribed);
-
sub separator_invert {
# The separator we hope we'll never encounter: 00000000
my $o_sep="\000";
@@ -1283,6 +1946,60 @@ sub imap2_folder_name {
return($h2_fold);
}
+
+sub foldersizes {
+
+ my ($side, $imap, @folders) = @_;
+ my $tot = 0;
+ my $tmess = 0;
+
+ print "++++ Calculating sizes\n";
+ foreach my $folder (@folders) {
+ my $stot = 0;
+ my $smess = 0;
+ printf("$side folder %-35s", "[$folder]");
+ unless($imap->exists($folder)) {
+ print("does not exist yet\n");
+ next;
+ }
+ unless ($imap->examine($folder)) {
+ warn
+ "$side Folder $folder: Could not examine: ",
+ $imap->LastError, "\n";
+ $nb_errors++;
+ next;
+ }
+
+ my $hash_ref = {};
+ my @msgs = select_msgs($imap);
+ $smess = scalar(@msgs);
+ @$hash_ref{@msgs} = (undef);
+ unless ($smess == 0) {
+ $imap->fetch_hash_2("RFC822.SIZE",$hash_ref) or die_clean("$@");
+ #print map {$hash_ref->{$_}->{"RFC822.SIZE"}, " "} keys %$hash_ref;
+ map {$stot += $hash_ref->{$_}->{"RFC822.SIZE"}} keys %$hash_ref;
+ }
+
+ printf(" Size: %9s", $stot);
+ printf(" Messages: %5s\n", $smess);
+ $tot += $stot;
+ $tmess += $smess;
+ }
+ print "Total size: $tot\n";
+ print "Total messages: $tmess\n";
+ print "Time: ", timenext(), " s\n";
+}
+
+sub timenext {
+ my ($timenow, $timerel);
+ # $timebefore is global, beurk !
+ $timenow = time;
+ $timerel = $timenow - $timebefore;
+ $timebefore = $timenow;
+ return($timerel);
+}
+
+
sub tests_flags_regex {
my $string;
@@ -1477,712 +2194,6 @@ sub flags_filter {
}
-# folder loop
-print "++++ Looping on each folder\n";
-
-FOLDER: foreach my $h1_fold (@h1_folders) {
-
- last FOLDER if $imap1->IsUnconnected();
- last FOLDER if $imap2->IsUnconnected();
-
- my $h2_fold = imap2_folder_name($h1_fold);
-
- printf("%-35s -> %-35s\n", "[$h1_fold]", "[$h2_fold]");
- unless ($imap1->select($h1_fold)) {
- warn
- "Host1 folder $h1_fold: Could not select: ",
- $imap1->LastError, "\n";
- $nb_errors++;
- next FOLDER;
- }
-
- 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 ($imap2->create($h2_fold)){
- warn "Couldn't create [$h2_fold]: ",
- $imap2->LastError,"\n";
- $nb_errors++;
- next FOLDER;
- }
- }
- else{
- next FOLDER;
- }
- }
-
- acls_sync($h1_fold, $h2_fold);
-
- unless ($imap2->select($h2_fold)) {
- warn
- "Host2 folder $h2_fold: Could not select: ",
- $imap2->LastError, "\n";
- $nb_errors++;
- next FOLDER;
- }
- my @select_results = $imap2->Results();
-
- #print "%%% @select_results\n";
- my $permanentflags2 = permanentflags(@select_results);
-
- if ($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{$h1_fold}) or $subscribe_all) {
- print "Subscribing to folder $h2_fold on destination server\n";
- unless($dry) { $imap2->subscribe($h2_fold) };
- }
-
- next FOLDER if ($justfolders);
-
- last FOLDER if $imap1->IsUnconnected();
- last FOLDER if $imap2->IsUnconnected();
-
- my @h1_msgs = select_msgs($imap1);
-
- $debug and print "LIST Host1: ", scalar(@h1_msgs), " messages [@h1_msgs]\n";
- # internal dates on host2 are after the ones on host1
- # normally...
- my @h2_msgs = select_msgs($imap2);
-
- $debug and print "LIST Host2: ", scalar(@h2_msgs), " messages [@h2_msgs]\n";
-
- my %h1_hash = ();
- my %h2_hash = ();
-
- $debug and print "Host1 folder [$h1_fold] parsing headers\n";
- last FOLDER if $imap1->IsUnconnected();
- last FOLDER if $imap2->IsUnconnected();
-
- my ($h1_heads_ref, $h1_fir_ref) = ({}, {});
- $h1_heads_ref = $imap1->parse_headers([@h1_msgs], @useheader) if (@h1_msgs);
- $debug and print "Time headers: ", timenext(), " s\n";
- last FOLDER if $imap1->IsUnconnected();
-
- @$h1_fir_ref{@h1_msgs} = (undef);
- $h1_fir_ref = $imap1->fetch_hash_2("FLAGS", "INTERNALDATE", "RFC822.SIZE", $h1_fir_ref)
- if (@h1_msgs);
- $debug and print "Time fir: ", timenext(), " s\n";
- unless ($h1_fir_ref) {
- warn
- "Host1 folder $h1_fold: Could not fetch_hash_2 ",
- scalar(@h1_msgs), " msgs: ", $imap1->LastError, "\n";
- $nb_errors++;
- next FOLDER;
- }
- last FOLDER if $imap1->IsUnconnected();
-
-
- my @h1_msgs_duplicate;
- foreach my $m (@h1_msgs) {
- my $rc = parse_header_msg($imap1, $m, $h1_heads_ref, $h1_fir_ref, "F", \%h1_hash);
- if (! defined($rc)) {
- my $h1_size = $h1_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
- print "+ Skipping msg #$m:$h1_size on host1 folder $h1_fold (no header so we ignore this message)\n";
- $total_bytes_skipped += $h1_size;
- $nb_msg_skipped += 1;
- $h1_nb_msg_noheader +=1;
- } elsif(0 == $rc) {
- # duplicate
- push(@h1_msgs_duplicate, $m);
- # duplicate, same id same size?
- my $h1_size = $h1_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
- $nb_msg_skipped += 1;
- $h1_total_bytes_duplicate += $h1_size;
- $h1_nb_msg_duplicate += 1;
- }
- }
- $debug and print "Time parsing headers on host1: ", timenext(), " s\n";
-
- $debug and print "Host2 folder [$h2_fold] parsing headers\n";
-
- my ($h2_heads_ref, $h2_fir_ref) = ({}, {});
- $h2_heads_ref = $imap2->parse_headers([@h2_msgs], @useheader) if (@h2_msgs);
- $debug and print "Time headers: ", timenext(), " s\n";
- last FOLDER if $imap2->IsUnconnected();
-
- @$h2_fir_ref{@h2_msgs} = (undef); # fetch_hash_2 can select by uid with last arg as ref
- $h2_fir_ref = $imap2->fetch_hash_2("FLAGS", "INTERNALDATE", "RFC822.SIZE", $h2_fir_ref)
- if (@h2_msgs);
- $debug and print "Time fir: ", timenext(), " s\n";
- last FOLDER if $imap2->IsUnconnected();
-
- my @h2_msgs_duplicate;
- foreach my $m (@h2_msgs) {
- my $rc = parse_header_msg($imap2, $m, $h2_heads_ref, $h2_fir_ref, "T", \%h2_hash);
- if (! defined($rc)) {
- my $h2_size = $h2_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
- print "+ Skipping msg #$m:$h2_size in host2 folder $h2_fold (no header so we ignore this message)\n";
- $h2_nb_msg_noheader += 1 ;
- } elsif(0 == $rc) {
- # duplicate
- my $h2_size = $h2_fir_ref->{$m}->{"RFC822.SIZE"} || 0;
- $h2_nb_msg_duplicate += 1;
- $h2_total_bytes_duplicate += $h2_size;
- push(@h2_msgs_duplicate, $m);
- }
- }
- $debug and print "Time parsing headers on host2: ", timenext(), " s\n";
-
- $debug and print "++++ Verifying [$h1_fold] -> [$h2_fold]\n";
- # messages in host1 that are not in host2
-
- my @h1_hash_keys_sorted_by_uid
- = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys(%h1_hash);
-
- #print map { $h1_hash{$_}{'m'} . " "} @h1_hash_keys_sorted_by_uid;
-
- my @h2_hash_keys_sorted_by_uid
- = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys(%h2_hash);
-
-
- if($delete2) {
- my @h2_expunge;
- foreach my $m_id (@h2_hash_keys_sorted_by_uid) {
- #print "$m_id ";
- unless (exists($h1_hash{$m_id})) {
- my $h2_msg = $h2_hash{$m_id}{'m'};
- my $h2_flags = $h2_hash{$m_id}{'F'} || "";
- my $isdel = $h2_flags =~ /\B\\Deleted\b/ ? 1 : 0;
- print "msg $h2_fold/$h2_msg deleted on host2 [$m_id]\n"
- if ! $isdel;
- push(@h2_expunge, $h2_msg) if $uidexpunge2;
- unless ($dry or $isdel) {
- $imap2->delete_message($h2_msg);
- $h2_nb_msg_deleted += 1;
- }
- }
- }
- foreach my $h2_msg (@h2_msgs_duplicate) {
- print "msg $h2_fold/$h2_msg deleted [duplicate] on host2\n";
- push(@h2_expunge, $h2_msg) if $uidexpunge2;
- unless ($dry) {
- $imap2->delete_message($h2_msg);
- $h2_nb_msg_deleted += 1;
- }
- }
-
- my $cnt = scalar @h2_expunge;
- if(@h2_expunge and !$imap2->can("uidexpunge")) {
- warn "uidexpunge not supported (< IMAPClient 3.17)\n";
- }
- elsif(@h2_expunge) {
- print "uidexpunge $cnt message(s)\n";
- $imap2->uidexpunge(\@h2_expunge) if !$dry;
- }
- }
-
- 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 $h1_size > $maxsize) {
- print "msg $h1_fold/$h1_msg skipping ($h1_size exceeds maxsize limit $maxsize bytes)\n";
- $total_bytes_skipped += $h1_size;
- $nb_msg_skipped += 1;
- next MESS;
- }
- unless (exists($h2_hash{$m_id})) {
- # copy
- $debug and print "msg $h1_fold/$h1_msg copying to $h2_fold\n";
- last FOLDER if $imap1->IsUnconnected();
- last FOLDER if $imap2->IsUnconnected();
- my $string;
- $string = $imap1->message_string($h1_msg);
- unless (defined($string)) {
- warn
- "- msg $h1_fold/$h1_msg could not fetch [$m_id $h1_size]: ",
- $imap1->LastError, "\n";
- $nb_errors++;
- $total_bytes_error += $h1_size;
- next MESS;
- }
-
- #my $message_file = "tmp_imapsync_$$";
- #$imap1->select($h1_fold);
- #unlink($message_file);
- #$imap1->message_to_file($message_file, $h1_msg) or do {
- # warn "Could not put message #$h1_msg to file $message_file",
- # $imap1->LastError;
- # $nb_errors++;
- # $total_bytes_error += $h1_size;
- # next MESS;
- #};
- #$string = file_to_string($message_file);
- #print "AAA1[$string]ZZZ\n";
- #unlink($message_file);
- if (@regexmess) {
- $string = regexmess($string);
-
- #string_to_file($string, $message_file);
- }
-
-
-
- sub tests_regexmess {
-
- ok("blabla" eq regexmess("blabla"), "regexmess, no regexmess, nothing to do");
-
- @regexmess = ('s/p/Z/g');
- ok("ZoZoZo" eq regexmess("popopo"), "regexmess, s/p/Z/g");
-
- @regexmess = 's{c}{C}gxms';
- ok("H1: abC\nH2: Cde\n\nBody abC"
- eq regexmess("H1: abc\nH2: cde\n\nBody abc"),
- "regexmess, c->C");
-
- @regexmess = 's{\AFrom\ }{From:}gxms';
- ok( ''
- eq regexmess(''),
- 'From mbox 1 add colon blank');
-
- ok( 'From:'
- eq regexmess('From '),
- 'From mbox 2 add colo');
-
- ok( "\n" . 'From '
- eq regexmess("\n" . 'From '),
- 'From mbox 3 add colo');
-
- ok( "From: zzz\n" . 'From '
- eq regexmess("From zzz\n" . 'From '),
- 'From mbox 4 add colo');
-
- @regexmess = 's{\AFrom\ [^\n]*(\n)?}{}gxms';
- ok( ''
- eq regexmess(''),
- 'From mbox 1 remove, blank');
-
- ok( ''
- eq regexmess('From '),
- 'From mbox 2 remove');
-
- ok( "\n" . 'From '
- eq regexmess("\n" . 'From '),
- 'From mbox 3 remove');
-
- #print "[", regexmess("From zzz\n" . 'From '), "]";
- ok( "" . 'From '
- eq regexmess("From zzz\n" . 'From '),
- 'From mbox 4 remove');
-
-
- ok(
-'Date: Sat, 10 Jul 2010 05:34:45 -0700
-From:
-
-Hello,
-Bye.'
- eq regexmess(
-'From zzz
-Date: Sat, 10 Jul 2010 05:34:45 -0700
-From:
-
-Hello,
-Bye.'
- ),
- 'From mbox 5 remove');
-
-
- }
-
- sub regexmess {
- my ($string) = @_;
- foreach my $regexmess (@regexmess) {
- $debug and print "eval \$string =~ $regexmess\n";
- eval("\$string =~ $regexmess");
- die_clean("error: eval regexmess '$regexmess': $@\n") if $@;
- }
- return($string);
- }
-
- $debug and print
- "=" x80, "\n",
- "F message content begin next line\n",
- $string,
- "F message content ended on previous line\n", "=" x 80, "\n";
- my $h1_date = "";
- if ($syncinternaldates) {
- $h1_date = $h1_idate;
- $debug and print "internal date from host1: [$h1_date]\n";
- $h1_date = good_date($h1_date);
- $debug and print "internal date from host1: [$h1_date] (fixed)\n";
- }
-
- if ($idatefromheader) {
-
- $h1_date = $imap1->get_header($h1_msg,"Date");
- $debug and print "header date from host1: [$h1_date]\n";
- $h1_date = good_date($h1_date);
- $debug and print "header date from host1: [$h1_date] (fixed)\n";
- }
-
- my $h1_flags = $h1_hash{$m_id}{'F'} || "";
- # RFC 2060: This flag can not be altered by any client
- $h1_flags =~ s@\\Recent\s?@@gi;
- $h1_flags = flags_regex($h1_flags) if @regexflag;
-
- $h1_flags = flags_filter($h1_flags, $permanentflags2) if ($permanentflags2);
-
- my $new_id;
- $debug and print "msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n";
- last FOLDER if $imap1->IsUnconnected();
- last FOLDER if $imap2->IsUnconnected();
- $h1_date = undef if ($h1_date eq "");
- unless ($dry) {
-
- if ($OSNAME eq "MSWin32") {
- $new_id = $imap2->append_string($h2_fold, $string, $h1_flags, $h1_date);
- }
- else {
- # just back to append_string since append_file 3.05 does not work.
- #$new_id = $imap2->append_file($h2_fold, $message_file, "", $h1_flags, $d);
- # append_string 3.05 does not work too some times with $d unset.
- $new_id = $imap2->append_string($h2_fold, $string, $h1_flags, $h1_date);
- }
- unless($new_id){
- no warnings 'uninitialized';
- warn "- msg $h1_fold/$h1_msg couldn't append (Subject:[".
- $imap1->subject($h1_msg)."]) to folder $h2_fold: ",
- $imap2->LastError, "\n";
- $nb_errors++;
- $total_bytes_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 "msg $h1_fold/$h1_msg copied to $h2_fold/$new_id\n";
- $total_bytes_transferred += $h1_size;
- $nb_msg_transferred += 1;
- if($delete) {
- print "msg $h1_fold/$h1_msg deleted on host1\n";
- unless($dry) {
- $imap1->delete_message($h1_msg);
- $h1_nb_msg_deleted += 1;
- last FOLDER if $imap1->IsUnconnected();
- $imap1->expunge() if ($expunge);
- last FOLDER if $imap1->IsUnconnected();
- }
- }
- }
- }
- else{
- $nb_msg_skipped_dry_mode += 1;
- }
- #unlink($message_file);
- next MESS;
- }
- else{
- #my $h2_size = $h2_hash{$m_id}{'s'};
- my $h2_msg = $h2_hash{$m_id}{'m'};
- #my $h2_idate = $h2_hash{$m_id}{'D'};
- $debug and print "msg $h1_fold/$h1_msg equals $h2_fold/$h2_msg\n";
- $total_bytes_skipped += $h1_size;
- $nb_msg_skipped += 1;
- }
-
- $fast and next MESS;
- #$debug and print "MESSAGE $m_id\n";
- my $h2_size = $h2_hash{$m_id}{'s'};
- my $h2_msg = $h2_hash{$m_id}{'m'};
-
- # used cached flag values for efficiency
- my $h1_flags = $h1_hash{$m_id}{'F'} || "";
- my $h2_flags = $h2_hash{$m_id}{'F'} || "";
-
- # RFC 2060: This flag can not be altered by any client
- $h1_flags =~ s@\\Recent\s?@@gi;
- $h1_flags = flags_regex($h1_flags) if @regexflag;
- $h1_flags = flags_filter($h1_flags, $permanentflags2) if ($permanentflags2);
-
-
- # compare flags - set flags if there a difference
- my @h1_flags = sort split(' ', $h1_flags );
- my @h2_flags = sort split(' ', $h2_flags );
- my $diff = compare_lists(\@h1_flags, \@h2_flags);
-
- $diff and $debug and print "msg $h2_fold/$h2_msg replacing h2 flags($h2_flags) with h1 flags($h1_flags)\n";
-
- # This sets flags so flags can be removed with this
- # When you remove a \Seen flag on host1 you want to it
- # to be removed on host2. Just add flags is not what
- # we need most of the time.
-
- if (!$dry and $diff and !$imap2->store($h2_msg, "FLAGS.SILENT (@h1_flags)") ) {
- warn "- msg $h2_fold/$h2_msg could not add flags @h1_flags",
- $imap2->LastError, "\n";
- #$nb_errors++;
- }
- last FOLDER if $imap2->IsUnconnected();
-
- $debug and do {
- my @h2_flags = @{ $imap2->flags($h2_msg) || [] };
- last FOLDER if $imap2->IsUnconnected();
-
- print "host1 flags: $h1_flags\n",
- "host2 flags: @h2_flags\n";
-
- print "Looking dates\n";
- #my $h1_idate = $imap1->internaldate($h1_msg);
- #my $h2_idate = $imap2->internaldate($h2_msg);
- my $h1_idate = $h1_hash{$m_id}{'D'};
- my $h2_idate = $h2_hash{$m_id}{'D'};
- print
- "host1 internal date: $h1_idate\n",
- "host2 internal date: $h2_idate\n";
-
- #unless ($h1_idate eq $h2_idate) {
- # print "!!! Dates differs !!!\n";
- #}
- };
- unless ($skipsize or ($h1_size == $h2_size)) {
- # Bad size
- print
- "- msg $h1_fold/$h1_msg size diff $h1_size != $h2_size $h2_fold/$h2_msg\n";
- $nb_errors++;
- }
- else {
- # Good
- $debug and print
- "msg $h1_fold/$h1_msg sizes ok $h1_size <=> $h2_size $h2_fold/$h2_msg\n";
- if($delete) {
- print "msg $h1_fold/$h1_msg deleted on host1\n";
- unless($dry) {
- $imap1->delete_message($h1_msg);
- $h1_nb_msg_deleted += 1;
- $imap1->expunge() if ($expunge);
- }
- }
- }
- }
- if ($expunge1){
- print "Expunging host1 folder $h1_fold\n";
- unless($dry) { $imap1->expunge() };
- }
- if ($expunge2){
- print "Expunging host2 folder $h2_fold\n";
- unless($dry) { $imap2->expunge() };
- }
-
-$debug and print "Time: ", timenext(), " s\n";
-}
-
-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($imap1,"host1 [$host1]");
-#
-# can be tested with a "killall /usr/bin/imapd" (or equivalent) in command line.
-#
-sub _filter {
- my $str = shift or return "";
- my $sz = 64;
- my $len = length($str);
- if ( ! $debug and $len > $sz*2 ) {
- my $beg = substr($str, 0, $sz);
- my $end = substr($str, -$sz, $sz);
- $str = $beg . "..." . $end;
- }
- $str =~ s/\012?\015$//;
- return "(len=$len) " . $str;
-}
-
-sub lost_connection {
- my($imap, $error_message) = @_;
- if ( $imap->IsUnconnected() ) {
- $nb_errors++;
- my $lcomm = $imap->LastIMAPCommand || "";
- my $einfo = $imap->LastError || @{$imap->History}[-1] || "";
-
- # if string is long try reduce to a more reasonable size
- $lcomm = _filter($lcomm);
- $einfo = _filter($einfo);
- warn("error: last command: $lcomm\n") if ($debug && $lcomm);
- warn("error: lost connection $error_message", $einfo, "\n");
- return(1);
- }else{
- return(0);
- }
-}
-
-$imap1->logout();
-$imap2->logout();
-
-my $host1_reconnect_count = $imap1->Reconnect_counter() || 0;
-my $host2_reconnect_count = $imap2->Reconnect_counter() || 0;
-
-$timeend = time();
-
-$timediff = $timeend - $timestart;
-
-stats();
-
-exit_clean(1) if($nb_errors);
-
-exit_clean(0);
-
-# END of main program
-
-# subroutines
-
-sub check_lib_version {
- $debug and print "IMAPClient $Mail::IMAPClient::VERSION\n";
- if ($Mail::IMAPClient::VERSION eq '2.2.9') {
- override_imapclient();
- return(1);
- }
- else{
- # 3.x.x is no longer buggy with imapsync.
- if ($allow3xx) {
- return(1);
- }else{
- return(0);
- }
- }
-}
-
-sub modules_VERSION {
-
- my @list_version;
-
- foreach my $module (qw(
-Mail::IMAPClient
-IO::Socket
-IO::Socket::SSL
-Digest::MD5
-Digest::HMAC_MD5
-Term::ReadKey))
- {
- my $v = "?";
-
- if (eval "require $module") {
- # module is here
- $v = eval "\$${module}::VERSION";
- }else{
- # no module
- $v = "?";
- }
- #print ("$module ", $v, "\n");
- push (@list_version, sprintf("%-20s %s\n", $module, $v));
- }
- return(@list_version);
-}
-
-# Construct a command line copy with passwords replaced by MASKED.
-sub command_line_nopassword {
- my @argv_copy = @_;
- my @argv_nopassword;
- while (@argv_copy) {
- my $arg = shift(@argv_copy); # option name or value
- if ($arg =~ m/-password[12]/) {
- shift(@argv_copy); # password value
- push(@argv_nopassword, $arg, "MASKED"); # option name and fake value
- }else{
- push(@argv_nopassword, $arg); # same option or value
- }
- }
- return("@argv_nopassword");
-}
-
-sub tests_command_line_nopassword {
-
- ok('' eq command_line_nopassword(), 'command_line_nopassword void');
- ok('--blabla' eq command_line_nopassword('--blabla'), 'command_line_nopassword --blabla');
- #print command_line_nopassword((qw{ --password1 secret1 })), "\n";
- ok('--password1 MASKED' eq command_line_nopassword(qw{ --password1 secret1}), 'command_line_nopassword --password1');
- ok('--blabla --password1 MASKED --blibli'
- eq command_line_nopassword(qw{ --blabla --password1 secret1 --blibli }), 'command_line_nopassword --password1 --blibli');
-
-
-}
-
-sub banner_imapsync {
-
- my @argv_copy = @_;
- my $banner_imapsync = join("",
- '$RCSfile: imapsync,v $ ',
- '$Revision: 1.350 $ ',
- '$Date: 2010/09/06 01:05:09 $ ',
- "\n",localhost_info(), "\n",
- "Command line used:\n",
- "$0 ", command_line_nopassword(@argv_copy), "\n",
- );
-}
-
-sub is_valid_directory {
- my $dir = shift;
- return(1) if (-d $dir and -r _ and -w _);
- # Trying to create it
- mkpath($dir) or die "Error creating tmpdir $tmpdir : $!";
- die "Error with tmpdir $tmpdir : $!" if not (-d $dir and -r _ and -w _);
- return(1);
-}
-
-
-
-
-sub write_pidfile {
- my $pidfile = shift;
-
- print "PID file is $pidfile\n";
- if (-e $pidfile) {
- warn "$pidfile already exists, overwriting it\n";
- }
- open(PIDFILE, ">$pidfile") or do {
- warn "Could not open $pidfile for writing";
- return undef;
- };
-
- print PIDFILE $PROCESS_ID;
- close PIDFILE;
- return($PROCESS_ID);
-}
-
-sub justconnect {
- my $imap1 = ();
- my $imap2 = ();
-
- $imap1 = connect_imap($host1, $port1, $debugimap1, $ssl1, $tls1);
- print "Host1 software: ", server_banner($imap1);
- print "Host1 capability: ", join(" ", $imap1->capability()), "\n";
- $imap2 = connect_imap($host2, $port2, $debugimap2, $ssl2, $tls2);
- print "Host2 software: ", server_banner($imap2);
- print "Host2 capability: ", join(" ", $imap2->capability()), "\n";
- $imap1->logout();
- $imap2->logout();
-
-}
-
-
-sub exit_clean {
- my $status = shift;
-
- unlink($pidfile);
- exit($status);
-}
-
-sub die_clean {
-
- unlink($pidfile);
- die @_;
-}
-
-sub missing_option {
- my ($option) = @_;
- die_clean("$option option must be used, run $0 --help for help\n");
-}
-
-
sub select_msgs {
my ($imap) = @_;
@@ -2214,6 +2225,85 @@ sub select_msgs {
return(@msgs);
}
+
+
+
+sub tests_regexmess {
+
+ ok("blabla" eq regexmess("blabla"), "regexmess, no regexmess, nothing to do");
+
+ @regexmess = ('s/p/Z/g');
+ ok("ZoZoZo" eq regexmess("popopo"), "regexmess, s/p/Z/g");
+
+ @regexmess = 's{c}{C}gxms';
+ ok("H1: abC\nH2: Cde\n\nBody abC"
+ eq regexmess("H1: abc\nH2: cde\n\nBody abc"),
+ "regexmess, c->C");
+
+ @regexmess = 's{\AFrom\ }{From:}gxms';
+ ok( ''
+ eq regexmess(''),
+ 'From mbox 1 add colon blank');
+
+ ok( 'From:'
+ eq regexmess('From '),
+ 'From mbox 2 add colo');
+
+ ok( "\n" . 'From '
+ eq regexmess("\n" . 'From '),
+ 'From mbox 3 add colo');
+
+ ok( "From: zzz\n" . 'From '
+ eq regexmess("From zzz\n" . 'From '),
+ 'From mbox 4 add colo');
+
+ @regexmess = 's{\AFrom\ [^\n]*(\n)?}{}gxms';
+ ok( ''
+ eq regexmess(''),
+ 'From mbox 1 remove, blank');
+
+ ok( ''
+ eq regexmess('From '),
+ 'From mbox 2 remove');
+
+ ok( "\n" . 'From '
+ eq regexmess("\n" . 'From '),
+ 'From mbox 3 remove');
+
+ #print "[", regexmess("From zzz\n" . 'From '), "]";
+ ok( "" . 'From '
+ eq regexmess("From zzz\n" . 'From '),
+ 'From mbox 4 remove');
+
+
+ ok(
+'Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.'
+ eq regexmess(
+'From zzz
+Date: Sat, 10 Jul 2010 05:34:45 -0700
+From:
+
+Hello,
+Bye.'
+ ),
+ 'From mbox 5 remove');
+}
+
+sub regexmess {
+ my ($string) = @_;
+ foreach my $regexmess (@regexmess) {
+ $debug and print "eval \$string =~ $regexmess\n";
+ eval("\$string =~ $regexmess");
+ die_clean("error: eval regexmess '$regexmess': $@\n") if $@;
+ }
+ return($string);
+}
+
+
sub stats {
print "++++ Statistics\n";
print "Transfer time : $timediff sec\n";
@@ -2237,6 +2327,8 @@ sub stats {
printf ("Average bandwidth rate : %.1f KiB/s\n", $total_bytes_transferred / 1024 / $timediff);
print "Reconnections to host1 : $host1_reconnect_count\n";
print "Reconnections to host2 : $host2_reconnect_count\n";
+ printf ("Memory consumption : %.1f MB\n", $memory_consumption / 1024 / 1024);
+ print "Memory/biggest message ratio : $memory_ratio\n";
print "Detected $nb_errors errors\n\n";
print $warn_release, "\n";
@@ -2291,10 +2383,12 @@ sub get_options {
"regexflag=s" => \@regexflag,
"delete!" => \$delete,
"delete2!" => \$delete2,
+ "delete2folders!" => \$delete2folders,
"syncinternaldates!" => \$syncinternaldates,
- "idatefromheader!" => \$idatefromheader,
+ "idatefromheader!" => \$idatefromheader,
"syncacls!" => \$syncacls,
"maxsize=i" => \$maxsize,
+ "minsize=i" => \$minsize,
"maxage=i" => \$maxage,
"minage=i" => \$minage,
"buffersize=i" => \$buffersize,
@@ -2340,6 +2434,7 @@ sub get_options {
"tmpdir=s" => \$tmpdir,
"pidfile=s" => \$pidfile,
"releasecheck!" => \$releasecheck,
+ "modules_version!" => \$modules_version,
);
$debug and print "get options: [$opt_ret]\n";
@@ -2399,20 +2494,19 @@ sub parse_header_msg {
$val =~ s/[\x80-\xff]/X/g;
# remove the first blanks (dbmail bug ?)
- # and uppercase header keywords
- # (dbmail and dovecot)
$val =~ s/^\s*(.+)$/$1/;
+
+ # and uppercase header line
+ # (dbmail and dovecot)
- #my $H = uc($h);
- my $H = "$h: $val";
+ my $H = uc("$h: $val");
# show stuff in debug mode
- $debug and print "${s}H $H:", $val, "\n";
+ $debug and print "${s}H $H", "\n";
if ($skipheader and $H =~ m/$skipheader/i) {
$debug and print "Skipping header $H\n";
next;
}
- #$headstr .= "$H:". $val;
$headstr .= "$H";
}
}
@@ -2480,11 +2574,26 @@ sub string_to_file {
close FILE;
}
+sub tests_is_a_release_number {
+ ok(is_a_release_number(1.351), 'is_a_release_number 1.351');
+ ok(is_a_release_number(42.4242), 'is_a_release_number 42.4242');
+ ok(is_a_release_number(imapsync_version()), 'is_a_release_number imapsync_version()');
+ ok(! is_a_release_number('blabla' ), '! is_a_release_number blabla');
+
+}
+
+sub is_a_release_number {
+ my $number = shift;
+
+ $number =~ m{\d\.\d+};
+}
sub check_last_release {
my $public_release = not_long('imapsync_version_lfo');
- return('') if ($public_release eq 'unknown');
+ return('unknown') if ($public_release eq 'unknown');
+ return('unknown') if (! is_a_release_number($public_release));
+ return('timeout') if ($public_release eq 'timeout');
my $imapsync_here = imapsync_version();
@@ -2496,17 +2605,32 @@ sub check_last_release {
}
sub imapsync_version {
- my $rcs = '$Id: imapsync,v 1.350 2010/09/06 01:05:09 gilles Exp gilles $ ';
+ my $rcs = '$Id: imapsync,v 1.366 2010/10/25 17:15:52 gilles Exp gilles $ ';
$rcs =~ m/,v (\d+\.\d+)/;
my $VERSION = ($1) ? $1: "UNKNOWN";
return($VERSION);
}
+sub tests_imapsync_basename {
+
+ ok('imapsync' eq imapsync_basename(), 'imapsync_basename: imapsync');
+ ok('blabla' ne imapsync_basename(), '! imapsync_basename: blabla');
+}
+
+sub imapsync_basename {
+
+ return basename($0);
+
+}
sub imapsync_version_lfo {
my $local_version = imapsync_version();
- my $agent_info = "$OSNAME system, perl $PERL_VERSION, Mail::IMAPClient $Mail::IMAPClient::VERSION";
+ my $imapsync_basename = imapsync_basename();
+ my $agent_info = "$OSNAME system, perl "
+ . sprintf("%vd", $PERL_VERSION)
+ . ", Mail::IMAPClient $Mail::IMAPClient::VERSION"
+ . " $imapsync_basename";
my $sock = new IO::Socket::INET (
PeerAddr => 'linux-france.org',
PeerPort => '80',
@@ -2524,23 +2648,42 @@ sub imapsync_version_lfo {
}
sub not_long {
-
+ #print "Entering not_long\n";
my ($func) = @_;
my $val;
- eval {
+
+ # Doesn't work with gethostbyname (see perlipc)
+ #local $SIG{ALRM} = sub { die "alarm\n" };
+
+ if ('MSWin32' eq $OSNAME) {
local $SIG{ALRM} = sub { die "alarm\n" };
- alarm 3;
+ }else{
+
+ POSIX::sigaction(SIGALRM,
+ POSIX::SigAction->new(sub { die "alarm" }))
+ or warn "Error setting SIGALRM handler: $!\n";
+ }
+
+ eval {
+
+ alarm(3);
#print $func, "\n";
{
no strict "refs";
+ #print "Calling $func\n";
$val = &$func();
+ #print "End of $func\n";
}
- alarm 0;
+ alarm(0);
};
if ($@) {
+ if ($@ =~ /alarm/) {
# timed out
- return('unknown') unless $@ eq "alarm\n"; # propagate unexpected errors
-
+ return('timeout');
+ }else{
+ alarm(0);
+ return('unknown'); # propagate unexpected errors
+ }
}else {
# didn't
return($val);
@@ -2566,7 +2709,8 @@ sub localhost_info {
sub usage {
my $localhost_info = localhost_info();
my $thank = thank_author();
- my $warn_release = check_last_release();
+ my $warn_release ='';
+ $warn_release = check_last_release() if (not defined($releasecheck));
print < : sets the size of a block of I/O.
---maxsize : skip messages larger than bytes
+--maxsize : skip messages larger (or equal) than bytes
+--minsize : skip messages smaller (or equal) than bytes
--maxage : skip messages older than days.
final stats (skipped) don't count older messages
see also --minage
@@ -2767,6 +2915,115 @@ sub good_date {
return($d);
}
+sub memory_consumption {
+ # memory consumed by imapsync until now in bytes
+ return((memory_consumption_of_pids())[0]);
+}
+
+sub memory_consumption_of_pids {
+
+ my @PID = (@_) ? @_ : ($PROCESS_ID);
+
+ #print "PIDs: @PID\n";
+ my @val;
+ if ('MSWin32' eq $OSNAME) {
+ @val = memory_consumption_of_pids_win32(@PID);
+ }else{
+ # Unix
+ my @ps = qx{ ps o vsz @PID };
+ shift @ps; # First line is column name "VSZ"
+ chomp @ps;
+ # convert to
+ @val = map { $_ * 1024 } @ps;
+ return(@val);
+ }
+}
+
+sub memory_consumption_of_pids_win32 {
+ # Windows
+ my @PID = @_;
+ my %PID;
+ # hash of pids as key values
+ map { $PID{$_}++ } @PID;
+
+ # Does not work but should reading the tasklist documentation
+ #@ps = qx{ tasklist /FI "PID eq @PID" };
+
+ my @ps = qx{ tasklist /NH /FO CSV };
+ #print "-" x 80, "\n", @ps, "-" x 80, "\n";
+ my @val;
+ foreach my $line (@ps) {
+ my($name, $pid, $mem) = (split(',', $line))[0,1,4];
+ next if (! $pid);
+ #print "[$name][$pid][$mem]";
+ if ($PID{remove_qq($pid)}) {
+ #print "MATCH !\n";
+ chomp($mem);
+ $mem = remove_qq($mem);
+ $mem = remove_Ko($mem);
+ $mem = remove_not_num($mem);
+ #print "[$mem]\n";
+ push(@val, $mem * 1024);
+ }
+ }
+ return(@val);
+}
+
+sub remove_not_num {
+
+ my $string = shift;
+ $string =~ tr/0-9//cd;
+ #print "tr [$string]\n";
+ return($string);
+}
+
+sub tests_remove_not_num {
+
+ ok('123' eq remove_not_num(123), 'remove_not_num( 123 )');
+ ok('123' eq remove_not_num('123'), "remove_not_num( '123' )");
+ ok('123' eq remove_not_num('12 3'), "remove_not_num( '12 3' )");
+ ok('123' eq remove_not_num('a 12 3 Ko'), "remove_not_num( 'a 12 3 Ko' )");
+}
+
+sub remove_Ko {
+ my $string = shift;
+ if ($string =~ /^(.*) Ko$/) {
+ return($1);
+ }else{
+ return($string);
+ }
+}
+
+sub remove_qq {
+ my $string = shift;
+ if ($string =~ /^"(.*)"$/) {
+ return($1);
+ }else{
+ return($string);
+ }
+}
+
+sub memory_consumption_ratio {
+
+ my ($base) = @_;
+ $base ||= 1;
+ my $consu = memory_consumption();
+ return($consu / $base);
+}
+
+sub tests_memory_consumption {
+
+ ok(print join("\n", memory_consumption_of_pids()), "\n");
+ ok(print join("\n", memory_consumption_of_pids('1')), "\n");
+ ok(print join("\n", memory_consumption_of_pids('1', $$)), "\n");
+
+ ok(print memory_consumption_ratio(), "\n");
+ ok(print memory_consumption_ratio(1), "\n");
+ ok(print memory_consumption_ratio(10), "\n");
+
+ ok(print memory_consumption(), "\n");
+}
+
sub tests_good_date {
ok('' eq good_date(), 'good_date no arg');
@@ -2780,11 +3037,65 @@ sub tests_good_date {
}
+
+sub tests_list_keys_in_2_not_in_1 {
+
+ my @list;
+ ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}');
+ ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}');
+ ok( 0 == compare_lists( ['a','b'], [ list_keys_in_2_not_in_1( {}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {} {a, b}');
+ ok( 0 == compare_lists( ['b'], [ list_keys_in_2_not_in_1( {'a' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a} {a, b}');
+ ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b} {a, b}');
+ ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
+ ok( 0 == compare_lists( ['b'], [ list_keys_in_2_not_in_1( {'a' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}');
+
+}
+
+sub list_keys_in_2_not_in_1 {
+
+ my $folders1_ref = shift;
+ my $folders2_ref = shift;
+ my @list;
+
+ foreach my $folder ( sort keys %$folders2_ref ) {
+ next if exists($folders1_ref->{$folder});
+ push(@list, $folder);
+ }
+ return(@list);
+}
+
+
+sub list_folders_in_2_not_in_1 {
+
+ my (@h2_folders_not_in_1, %h2_folders_not_in_1);
+ @h2_folders_not_in_1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all);
+ map { $h2_folders_not_in_1{$_} = 1} @h2_folders_not_in_1;
+ @h2_folders_not_in_1 = list_keys_in_2_not_in_1( \%h2_folders_from_1, \%h2_folders_not_in_1);
+
+ return( reverse @h2_folders_not_in_1 );
+}
+
+sub delete_folders_in_2_not_in_1 {
+
+ my $dry_message = '';
+ $dry_message = "\t(not really since --dry mode)" if $dry;
+ foreach my $folder (@h2_folders_not_in_1) {
+
+ my $res = $dry; # always success in dry mode!
+ $res = $imap2->delete($folder) if ( ! $dry ) ;
+ if ($res) {
+ print "Delete $folder", "$dry_message", "\n";
+ }else{
+ print "Delete $folder failure", "\n";
+ }
+ }
+}
+
sub tests_debug {
SKIP: {
skip "No test in normal run" if (not $tests_debug);
- tests_good_date();
+ tests_list_keys_in_2_not_in_1();
}
}
@@ -2801,6 +3112,12 @@ sub tests {
tests_imap2_folder_name();
tests_command_line_nopassword();
tests_good_date();
+ tests_max();
+ tests_remove_not_num();
+ tests_memory_consumption();
+ tests_is_a_release_number();
+ tests_imapsync_basename();
+ tests_list_keys_in_2_not_in_1();
}
}
@@ -3333,14 +3650,18 @@ use constant NonFolderArg => 1; # Value to pass to Massage to
"BODY" . ( $self->Peek ? '.PEEK[]' : '[]' ) :
"RFC822" . ( $self->Peek ? '.PEEK' : '' ) ;
+ #print "Message_string Beg fetch:\n", memory_consumption();
$self->fetch($msg,$cmd) or return undef;
+ #print "Message_string End fetch:\n", memory_consumption();
my $string = "";
+
foreach my $result (@{$self->{"History"}{$self->Transaction}}) {
$string .= $result->[DATA]
if defined($result) and $self->_is_literal($result) ;
}
+ #print "Message_string End string:\n", memory_consumption();
# BUG? should probably return undef if length != expected
# No bug, somme servers are buggy.
@@ -3426,6 +3747,8 @@ no warnings 'once';
my $retry = $self->Reconnectretry || 0;
my ( $rc, @err );
+ #print "@_ Beg _imap_command:\n", memory_consumption();
+
# LastError (if set) will be overwritten masking any earlier errors
while ( $tries++ <= $retry ) {
# do command on the first try or if Connected (reconnect ongoing)
@@ -3472,7 +3795,7 @@ no warnings 'once';
}
$self->LastError( join( "; ", @info ) );
}
-
+ #print "@_ End _imap_command:\n", memory_consumption();
return $rc;
};
@@ -3495,8 +3818,9 @@ no warnings 'once';
$string = "$count $string" ;
+ #print "$string\n", memory_consumption();
$self->_record($count,[ 0, "INPUT", "$string\x0d\x0a"] );
-
+ #print "\n2 $count\n", memory_consumption();
my $feedback = $self->_send_line("$string");
unless ($feedback) {
@@ -3514,6 +3838,7 @@ no warnings 'once';
$output = $self->_read_line or return undef;
for my $o (@$output) {
+
$self->_record($count,$o); # $o is a ref
# $self->_debug("Received from readline: ${\($o->[DATA])}<>\n");
next unless $self->_is_output($o);
@@ -3529,7 +3854,7 @@ no warnings 'once';
}
}
}
-
+ #print "$string: returned $code\n", memory_consumption();
# $self->_debug("Command $string: returned $code\n");
return $code =~ /^OK|$qgood/im ? $self : undef ;
@@ -3600,6 +3925,7 @@ no warnings 'once';
$iBuffer eq ""
) {
+ #print memory_consumption();
my $transno = $self->Transaction; # used below in several places
if ($timeout) {
vec($rvec, fileno($self->Socket), 1) = 1;
@@ -3663,6 +3989,7 @@ no warnings 'once';
$count += $ret;
LINES: while ( $iBuffer =~ s/^(.*?\x0d?\x0a)// ) {
my $current_line = $1;
+ #print memory_consumption();
# $self->_debug("BUFFER: pulled from buffer: ${current_line}\n" .
# "and left with buffer contents of: ${iBuffer}\n");
@@ -3841,93 +4168,6 @@ no warnings 'once';
# End of sub override_imapclient (yes, very bad indentation)
}
-sub myconnect {
- my $self = shift;
-
- $debug and print "Entering myconnect\n";
- %$self = (%$self, @_);
-
- my $sock = (($self->Ssl) ? IO::Socket::SSL->new : IO::Socket::INET->new);
- my $dp = ($self->Ssl ? 'imaps(993)' : 'imap(143)');
-
- $debug and print "Calling configure\n";
- my $ret = $sock->configure({
- PeerAddr => $self->Server ,
- PeerPort => $self->Port||$dp ,
- Proto => 'tcp' ,
- Timeout => $self->Timeout||0 ,
- Debug => $self->Debug ,
- });
- unless ( defined($ret) ) {
- $self->LastError( "$@\n");
- $@ = "$@";
- carp "$@"
- unless defined wantarray;
- return undef;
- }
- $sock->autoflush(1);
-
- my $banner = $sock->getline();
- $debug and print "Read: $banner";
-
- $self->Banner($banner);
- $self->RawSocket2($sock);
- $self->State(Connected);
-
- if ($self->Tls) {
- $debug and print "Calling starttls\n";
-
- my $banner = starttls($self);
- $debug and print "End starttls: $banner\n";
- }
-
- $self->Ignoresizeerrors($allowsizemismatch);
-
- if ($self->User and $self->Password) {
- $debug and print "Calling login\n";
- return $self->login ;
- }
- else {
- return $self;
- }
-}
-
-
-sub starttls {
- my $self = shift;
- my $socket = $self->RawSocket2();
-
- $debug and print "Entering starttls\n";
- my $banner = $self->Banner();
- $debug and print $banner;
- unless ($banner =~ /^\* OK \[CAPABILITY.*STARTTLS.*\]/) {
- die_clean( "No STARTTLS capability: $banner" );
- }
- print $socket, "\n";
- print $socket "z00 STARTTLS\015\012";
- my $txt = $socket->getline();
- $debug and print "Read: $txt";
- unless($txt =~ /^z00 OK/){
- die_clean( "Invalid response for STARTTLS: $txt\n" );
- }
- $debug and print "Calling start_SSL\n";
- unless(IO::Socket::SSL->start_SSL($socket,
- {
- SSL_version => "TLSV1",
- SSL_startHandshake => 1,
- SSL_verify_depth => 1,
- }))
- {
- die_clean( "Couldn't start TLS: ".IO::Socket::SSL::errstr()."\n");
- }
- if (ref($socket) ne "IO::Socket::SSL") {
- die_clean( "Socket has NOT been converted to SSL");
- }else{
- $debug and print "Socket successfuly converted to SSL\n";
- }
- $banner;
-}
-
# IMAPClient 2.2.9 3.xx ads
package Mail::IMAPClient;
diff --git a/index.shtml b/index.shtml
index a8f015d..35f2adc 100644
--- a/index.shtml
+++ b/index.shtml
@@ -5,7 +5,7 @@
imapsync
-
+
@@ -13,15 +13,37 @@
+
+
+
+
+
+
+
+
+
+
+
What is imapsync?
imapsync software is a command line tool allowing incremental and
-recursive imap transfers from one mailbox to another, both anywhere on the internet.
+recursive imap transfers from one mailbox to another, both anywhere on the internet
+or in your local network.
imapsync is useful for imap account migration or imap account backup .
@@ -36,26 +58,37 @@ where the user plays independently on both sides. Use offlineimap
AUTHOR
Gilles LAMIRAL
- Email: lamiral@linux-france.org
+ Email: gilles.lamiral@laposte.net
- Feedback good or bad is often welcome.
+ Good feedback is always welcome, bad feedback is often welcome.
- A good place to talk about imapsync is the public
+
A nice place to talk about imapsync is the public
imapsync mailing-list (see below).
- Gilles LAMIRAL earns his living writing, installing, configuring and
- teaching free, open and gratis softwares.
- Do not hesitate to pay him for that services.
-
If you use imapsync as a professionnal worker you may
read this call
- for feedback.
+ for rewarding.
+ Other ways to consider the situation:
+
+ stop beeing a
+ leech .
+
+ think about
+ helping back
+ a software developper and helper.
+
+
+
+
imapsync call for donation
Are you happy with this free , open and gratis software?
-Then you can help the author to maintain imapsync and support happy (or unhappy) users: YOU!
+Then you can help me back to maintain imapsync
+and support you!
+I will personally thank each donation
+with an email and add an entry in the imapsync CREDITS file.
-Or offer him a book on his
+You can also offer me a book on my imapsync amazon
wishlist
+
-Thanks in advance!
+
+If you prefer making your donation with cash or cheque then my postal address is:
+Gilles LAMIRAL
+4 La Billais
+35580 Baulon
+FRANCE
+
+
+My phone numbers are:
++33 951 84 42 42 (home/work)
++33 620 79 76 06 (mobile)
++33 956 84 42 42 (fax)
+
+
+Thanks in advance!
-I code new features for free when I have time and when I find it useful.
-If you really want a feature you can donate money and I'll code it.
+
I code new features and fix bugs for free when I have time and when I find it useful.
+If you really want a feature or a fix you can donate money and my next development time
+will be to code it or fix it.
Some features and their time/money to be done evaluation
DONE Feature Time guessed Time spent Money received Money needed
- No Efficient Gmail backup 8 hours 80 min 0 $ 240 $
- No Speedup 50% 10 hours 80 min 10 $ 300 $
- No Backup to files 8 hours 60 min 0 $ 240 $
- No --deletefolder2 3 hours 30 min 0 $ 90 $
- No NTLM auth 3 hours 60 min 0 $ 90 $
- Yes Win32 imapsync.exe 8 hours 520 min 0 $ 240 $
- Yes Fix capability changes 1 hour 80 min 0 $ 30 $
- Yes Large mailbox --maxage 4 hours 270 min 0 $ 120 $
+ No Efficient Gmail backup 8 hours 80 min 0 $ 240 $
+ No Speedup 50% 10 hours 80 min 10 $ 300 $
+ No Backup to files 8 hours 60 min 0 $ 240 $
+Yes --delete2folders 3 hours 270 min 90 $ 0 $
+ No NTLM auth 3 hours 60 min 0 $ 90 $
+ Yes Win32 imapsync.exe 8 hours 520 min 0 $ 240 $
+ Yes Win32 bug fixes various 370 min 100 $ 85 $
+ Yes Fix capability changes 1 hour 80 min 0 $ 30 $
+ Yes Large mailbox --maxage 4 hours 270 min 0 $ 120 $
COPYING
@@ -175,8 +234,8 @@ If you really want a feature you can donate money and I'll code it.
This document last modified
-$Id: index.shtml,v 1.22 2010/08/21 13:39:35 gilles Exp gilles $
+$Id: index.shtml,v 1.31 2010/10/25 00:05:35 gilles Exp gilles $
-