ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/syncmail/folder.pm
Revision: 1.2
Committed: Sun Oct 28 03:51:24 2001 UTC (22 years, 6 months ago) by root
Branch: MAIN
Changes since 1.1: +36 -26 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 package folder;
2    
3     BEGIN { *slog = \&::slog };
4    
5 root 1.2 use Digest::SHA1;
6    
7 root 1.1 use constant MDIFVERSION => 1;
8    
9     sub new {
10     my $class = shift;
11     my %arg = @_;
12     bless {
13     path => "$::PREFIX/$arg{name}",
14     %arg,
15     }, $class;
16     }
17    
18     sub dirty {
19     $_[0]{dirty} = 1;
20     }
21    
22     sub DESTROY {
23     $_[0]->write_mdif;
24     }
25    
26     # parse_mbox(mbox-file-path, callback)
27     # callback gets called with \$header and \$body,
28     # $header includes the mbox From_ line without
29     # the leading From_ itself.
30     sub parse_mbox {
31     my ($path, $cb) = @_;
32    
33     open my $fh, "<", $path
34     or die "$path: $!";
35    
36     local $/ = "\n\n";
37    
38     my ($head, $body, $offs);
39    
40     5 == read $fh, $head, 5
41     or return;
42    
43     $head eq "From "
44     or return;
45    
46     $offs = 0;
47     while (defined ($head = <$fh>)) {
48     $head =~ /^.*? [A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] \d\d:\d\d:\d\d(?: [+-]\d\d\d\d)? \d\d(?:\d\d)\n/
49     or die "$path: not standard mbox format header:\n$head\n";
50    
51     local $/ = "\nFrom ";
52     # NEVER enable this. content-length simply is broken by design
53     #if ($head =~ /^Content-Length:\s+(\d+)$/im) {
54     # $1 <= read $fh, $body, $1 + 5
55     # or die "$path: partial message in mbox";
56     #} else {
57     $body = <$fh>;
58     #}
59     chomp $body;
60     $cb->($offs, \$head, \$body);
61     $offs = (tell $fh) - 5;
62     ::give unless ++$ecnt & 1023;
63     }
64    
65     1;
66     }
67    
68     sub conf_path {
69     (my $conf = $_[0]{path}) =~ s%([^/]+$)%.$1.mdif%;
70     $conf;
71     }
72    
73     sub read_mdif {
74     my $self = shift;
75     my $path = $self->conf_path;
76    
77     return if $self->{idx};
78    
79     open my $fh, "<", $path
80     or return;
81    
82     defined ($_ = <$fh>)
83     or die "$path: empty mdif file\n";
84    
85     do {
86     if ($_ eq "[SYNCMAIL]\n") {
87     while (<$fh>) {
88     last unless /^([a-z]+)\s*=\s*(.*)\n$/;
89     $self->{$1} = $2;
90     }
91     } elsif ($_ eq "[HOSTS]\n") {
92     while (<$fh>) {
93     last unless /^([^[].*)=(.*)\n$/;
94     $self->{host}{$1} = $2;
95     }
96     } elsif (/^\[DIFF (\d+)\]\n$/) {
97     my $mtime = $1;
98 root 1.2 my (@add, @del);
99 root 1.1 while (<$fh>) {
100 root 1.2 last unless /^([+-])(.*)\n$/;
101     if ($1 eq "+") {
102     push @add, $2;
103     } else {
104     push @del, $2;
105     }
106 root 1.1 }
107 root 1.2 unshift @{$self->{diff}}, [$mtime, \@add, \@del];
108 root 1.1 } elsif ($_ eq "[INDEX]\n") {
109     my @idx;
110     while (<$fh>) {
111     last unless /^(\d+)=(.*)\n$/;
112     push @idx, [$1, $2];
113     }
114     $self->{idx} = \@idx;
115     } elsif (/^#/) {
116     $_ = <$fh>;
117     # nop
118     } else {
119     die "$path: unparseable section '$_'\n";
120     }
121     } while defined $_;
122    
123     $self->{version} <= MDIFVERSION
124     or die "$path: version mismatch ($self->{version} found, <".MDIFVERSION." expected)\n";
125     }
126    
127     sub write_mdif {
128     my $self = shift;
129     my $path = $self->conf_path;
130    
131     return unless $self->{dirty};
132    
133     open my $fh, ">", "$path~"
134     or die "$path~: $!";
135    
136     print $fh "# automatically generated, do NOT edit\n";
137    
138     print $fh "[SYNCMAIL]\n";
139     print $fh "$_=$self->{$_}\n" for (qw(fsize mtime version));
140    
141     print $fh "[HOSTS]\n";
142     while (my ($k,$v) = each %{$self->{host}}) {
143     print $fh "$k=$v\n";
144     }
145    
146     print $fh "[INDEX]\n";
147     print $fh "$_->[0]=$_->[1]\n" for @{$self->{idx}};
148    
149     for (reverse @{$self->{diff}}) {
150     print $fh "[DIFF $_->[0]]\n";
151 root 1.2 print $fh "+$_\n" for @{$_->[1]};
152     print $fh "-$_\n" for @{$_->[2]};
153 root 1.1 }
154    
155     close $fh
156     or die "$path~: unable to create updated .mdif: $!";
157    
158     rename "$path~", $path;
159    
160     delete $self->{dirty};
161     }
162    
163 root 1.2 if (1) {
164     use OpenSSL ();
165     *hash = \&OpenSSL::Digest::sha1_hex;
166     } elsif (0) {
167     # use Digest::SHA1;
168     my $digest = new Digest::SHA1;
169     *hash = sub {
170     $digest->reset;
171     $digest->add(@_);
172     $mid = $digest->hexdigest;
173     };
174     }
175    
176 root 1.1 sub gendiff {
177     my ($d1, $d2) = @_;
178    
179 root 1.2 my (@add, @del);
180 root 1.1 my (%d1, %d2);
181    
182     for (@$d2) {
183     undef $d2{$_->[1]};
184     }
185    
186     # delete msgs in d1 but not in d2
187     for (@$d1) {
188     undef $d1{$_->[1]};
189 root 1.2 push @del, $_->[1] unless exists $d2{$_->[1]};
190 root 1.1 }
191     %d2 = (); # conserve memory
192    
193     # add msgs in d2 but not in d1
194     for (@$d2) {
195 root 1.2 push @add, $_->[1] unless exists $d1{$_->[1]};
196 root 1.1 }
197    
198 root 1.2 (\@add, \@del);
199 root 1.1 }
200    
201     sub check {
202     my $self = shift;
203     my $path = $self->{path};
204     my $conf = $self->conf_path;
205     my $guard = $::lockdisk->guard;
206    
207     slog 3, "checking $path\n";
208    
209     if (stat $path) {
210     my ($fsize, $mtime) = (stat _)[7, 9];
211    
212     if (open my $fh, "<", $conf) {
213     my %conf;
214     <$fh>; # skip initial comment
215     <$fh> eq "[SYNCMAIL]\n"
216     or die "$conf: format error";
217     while (<$fh> =~ /^([a-z]+)\s*=\s*(.*)$/) {
218     $conf{$1} = $2;
219     }
220     return 1 if $fsize == $conf{fsize}
221     && $mtime == $conf{mtime};
222    
223     $conf{mtime} <= $mtime
224     or die "$path: folder older than mdif";
225     }
226    
227     slog 2, "updating $path\n";
228    
229     my @idx;
230    
231     parse_mbox $path, sub {
232     my ($offs, $head, $body) = @_;
233 root 1.2 push @idx, [$offs, hash($$head, "\0", $$body)];
234 root 1.1 } or return ();
235    
236     $self->read_mdif;
237    
238 root 1.2 $self->{version} ||= MDIFVERSION;
239     my ($add, $del) = gendiff $self->{idx}, \@idx;
240     push @{$self->{diff}}, [
241     $mtime,
242     $add, $del,
243     ] if @$add || @$del;
244 root 1.1
245     $self->{fsize} = $fsize;
246     $self->{mtime} = $mtime;
247     $self->{idx} = \@idx;
248    
249     $self->dirty;
250    
251     return 2;
252     } else {
253     slog 2, "$path: no longer exists\n";
254     unlink $conf;
255    
256     return ();
257     }
258     }
259    
260     1;
261