ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/syncmail/folder.pm
Revision: 1.1
Committed: Sat Oct 27 23:53:49 2001 UTC (22 years, 6 months ago) by root
Branch: MAIN
Log Message:
*** empty log message ***

File Contents

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