#!/usr/local/bin/perl -w use strict; use IO::Handle; use IO::File; use Errno; my @mapping= ( [qw(D .)], [qw(F tick)], [qw(P forward)], [qw(R reply)], [qw(S read)], [qw(T expire)], ); sub die_usage() { die("usage: marksync {--f2m|--m2f} nnmaildir-directory"); } sub die_err(@) { die('unable to ', @_, ': ', $!, "\n"); } sub die_open($ ) { die_err('open ', $_[0]); } sub die_link($$) { die_err('link ', $_[0], ' to ', $_[1]); } sub die_mkdir($ ) { die_err('mkdir ', $_[0]); } @ARGV==2 or die_usage(); my $mode=$ARGV[0]; $mode eq '--f2m' or $mode eq '--m2f' or die_usage(); sub file_exists() { return ($! != Errno::ENOENT); } sub do_create($ ) { my ($path)=@_; my $idx=0; for (;;) { substr($path, $idx)=~m:^(/*):; $idx+=length($1); $idx=index($path, '/', $idx); last if $idx<0; my $dir=substr($path, 0, $idx); mkdir($dir) or $!==Errno::EEXIST or die_mkdir($dir); } my $file=IO::File->new(); $file->open($path, O_WRONLY|O_CREAT, 0600) or die_open($path); $file->close(); } sub do_rename($$) { rename($_[0], $_[1]) or die_err('rename ', $_[0], ' to ', $_[1]); } sub listdir($$) { my ($dirpath, $fullpath)=@_; my $dir=IO::Handle->new(); opendir($dir, $dirpath) or die_open($ARGV[1]); my @files=readdir($dir); closedir($dir); @files=grep(/^[^.]/, @files); if ($fullpath) { @files=map($dirpath.'/'.$_, @files); } return @files; } my $dir=$ARGV[1]; $dir=~s:/*\z::; foreach my $maildir (listdir($dir, 1)) { my $cur=$maildir.'/cur'; foreach my $msg (listdir($cur, 0)) { my $info_idx=index($msg, ':'); my $uniq=substr($msg, 0, $info_idx); my $info=substr($msg, $info_idx); if ($info!~/^:2,/) { next; } if ($mode eq '--m2f') { # Update the maildir flags my $new_flags=''; foreach my $map (@mapping) { my ($flag, $mark)=@$map; my $markpath=$maildir.'/.nnmaildir/marks/'.$mark.'/'.$uniq; if ($mark eq '.') # This maildir flag has no corresponding Gnus mark; # just preserve it if it's there. { if (index($info, $flag)>=0) { $new_flags.=$flag; } } elsif (stat($markpath)) { $new_flags.=$flag; } elsif (file_exists()) { die_err('stat ', $markpath); } } my $new_info=':2,'.$new_flags; if ($info ne $new_info) { do_rename($cur.'/'.$msg, $cur.'/'.$uniq.$new_info); } } else { # --f2m; Update the Gnus marks foreach my $map (@mapping) { my ($flag, $mark)=@$map; if ($mark eq '.') { next; } my $markdir=$maildir.'/.nnmaildir/marks/'.$mark; my $markpath=$markdir.'/'.$uniq; if (index($info, $flag)>=0) { my $linkpath=$markdir.'/:'; if (link($linkpath, $markpath) or $!==Errno::EEXIST) { next; } if (not file_exists()) { do_create($linkpath); } elsif ($!==Errno::EMLINK) { do_create($linkpath.'{new}'); do_rename($linkpath.'{new}', $linkpath); } else { die_link($linkpath, $markpath); } link($linkpath, $markpath) or $!==Errno::EEXIST or die_link($linkpath, $markpath); } else { if (unlink($markpath)<0 and file_exists()) { die_err('unlink ', $markpath); } } } } } } # tell the perl interpreter there's no more code after this __END__ =head1 NAME C - synchronizes maildir flags with Gnus marks =head1 SYNOPSIS marksync --f2m /path/to/nnmaildir/server/directory marksync --m2f /path/to/nnmaildir/server/directory =head1 DESCRIPTION C is the path to your nnmaildir server directory, containing a set of maildirs (or symlinks to maildirs). C takes the information from the maildir message filename flags and copies it to the Gnus mark storage; you can run this after using mutt/IMAP/whatever and before you start Gnus. C copies information in the other direction, for use after you've run Gnus and are going to use mutt/IMAP/whatever. Don't run marksync in either mode while Gnus is running: --f2m could erase changes made since Gnus started, and --m2f will make it harder for Gnus to find your message files - they may appear to have been deleted, although Gnus will find them again eventually. =cut