… | |
… | |
23 | $mid = $digest->hexdigest; |
23 | $mid = $digest->hexdigest; |
24 | }; |
24 | }; |
25 | } |
25 | } |
26 | } |
26 | } |
27 | |
27 | |
28 | sub flushfh { |
28 | sub fflush { |
29 | my $oldfh = select $_[0]; |
29 | my $oldfh = select $_[0]; |
30 | $| = 1; |
30 | $| = 1; $| = 0; |
31 | select $oldfh; |
31 | select $oldfh; |
32 | } |
32 | } |
33 | |
33 | |
34 | # rename a file and fsync the directory |
34 | # rename a file and fsync the directory |
35 | sub replace { |
35 | sub replace { |
… | |
… | |
62 | sub dirty { |
62 | sub dirty { |
63 | $_[0]{dirty} = 1; |
63 | $_[0]{dirty} = 1; |
64 | } |
64 | } |
65 | |
65 | |
66 | sub DESTROY { |
66 | sub DESTROY { |
67 | $_[0]->write_mdif; |
67 | #$_[0]->write_mdif; # do NOT! |
68 | } |
68 | } |
69 | |
69 | |
70 | # parse_mbox(mbox-file-path, callback) |
70 | # parse_mbox(mbox-file-path, callback) |
71 | # callback gets called with \$header and \$body, |
71 | # callback gets called with \$header and \$body, |
72 | # $header includes the mbox From_ line without |
72 | # $header includes the mbox From_ line without |
… | |
… | |
190 | print $fh "[DIFF $_->[0]]\n"; |
190 | print $fh "[DIFF $_->[0]]\n"; |
191 | print $fh "+$_\n" for @{$_->[1]}; |
191 | print $fh "+$_\n" for @{$_->[1]}; |
192 | print $fh "-$_\n" for @{$_->[2]}; |
192 | print $fh "-$_\n" for @{$_->[2]}; |
193 | } |
193 | } |
194 | |
194 | |
195 | flushfh $fh; |
195 | fflush($fh); |
196 | File::Sync::fsync($fh); |
196 | File::Sync::fsync($fh); |
197 | close $fh |
197 | close $fh |
198 | or die "$path~: unable to create updated .mdif: $!"; |
198 | or die "$path~: unable to create updated .mdif: $!"; |
199 | |
199 | |
200 | replace("$path~", $path); |
200 | replace("$path~", $path); |
… | |
… | |
226 | |
226 | |
227 | push @{$self->{diff}}, [ |
227 | push @{$self->{diff}}, [ |
228 | $self->{ctime}, |
228 | $self->{ctime}, |
229 | \@add, \@del, |
229 | \@add, \@del, |
230 | ] if @add || @del; |
230 | ] if @add || @del; |
231 | } |
|
|
232 | |
|
|
233 | sub check { |
|
|
234 | my $self = shift; |
|
|
235 | my $path = $self->{path}; |
|
|
236 | my $conf = $self->conf_path; |
|
|
237 | my $guard = $::lockdisk->guard; |
|
|
238 | |
|
|
239 | slog 3, "checking $path\n"; |
|
|
240 | |
|
|
241 | stat $path |
|
|
242 | or die "$path: $!"; |
|
|
243 | |
|
|
244 | my ($fsize, $mtime) = (stat _)[7, 9]; |
|
|
245 | |
|
|
246 | if (open my $fh, "<", $conf) { |
|
|
247 | my %conf; |
|
|
248 | <$fh>; # skip initial comment |
|
|
249 | <$fh> eq "[SYNCMAIL]\n" |
|
|
250 | or die "$conf: format error"; |
|
|
251 | while (<$fh> =~ /^([a-z]+)\s*=\s*(.*)$/) { |
|
|
252 | $conf{$1} = $2; |
|
|
253 | } |
|
|
254 | return 1 if $fsize == $conf{fsize} |
|
|
255 | && $mtime == $conf{mtime}; |
|
|
256 | |
|
|
257 | $conf{mtime} <= $mtime |
|
|
258 | or die "$path: folder older than mdif"; |
|
|
259 | } |
|
|
260 | |
|
|
261 | slog 2, "updating $path\n"; |
|
|
262 | |
|
|
263 | my @idx; |
|
|
264 | |
|
|
265 | parse_mbox $self->{fh}, sub { |
|
|
266 | my ($offs, $head, $body) = @_; |
|
|
267 | push @idx, [$offs, hash($$head, "\0", $$body)]; |
|
|
268 | } or die "$path: no valid mbox file"; |
|
|
269 | |
|
|
270 | $self->read_mdif; |
|
|
271 | |
|
|
272 | $self->{version} ||= MDIFVERSION; |
|
|
273 | $self->{ctime} = time; |
|
|
274 | |
|
|
275 | $self->gendiff($self->{idx}, \@idx); |
|
|
276 | |
|
|
277 | $self->{fsize} = $fsize; |
|
|
278 | $self->{mtime} = $mtime; |
|
|
279 | $self->{idx} = \@idx; |
|
|
280 | |
|
|
281 | $self->dirty; |
|
|
282 | $self->write_mdif; |
|
|
283 | } |
|
|
284 | |
|
|
285 | sub inventory { |
|
|
286 | hash sort map { $_->[1] } @{$_[0]{idx}}; |
|
|
287 | } |
231 | } |
288 | |
232 | |
289 | sub open { |
233 | sub open { |
290 | my ($self, $rw) = @_; |
234 | my ($self, $rw) = @_; |
291 | |
235 | |
… | |
… | |
297 | 0666 |
241 | 0666 |
298 | or die "$self->{path}: $!"; |
242 | or die "$self->{path}: $!"; |
299 | 0 == setlkw(fileno $self->{fh}, $rw ? 2 : 1) |
243 | 0 == setlkw(fileno $self->{fh}, $rw ? 2 : 1) |
300 | or die "$self->{path}: $!"; |
244 | or die "$self->{path}: $!"; |
301 | |
245 | |
|
|
246 | $self->check; |
302 | } |
247 | } |
303 | } |
248 | } |
304 | |
249 | |
305 | sub close { |
250 | sub close { |
306 | my $self = shift; |
251 | my $self = shift; |
307 | |
252 | |
|
|
253 | if ($self->{rw} && $self->{fh}) { |
308 | flushfh $self->{fh}; |
254 | fflush($self->{fh}); |
309 | File::Sync::fsync($self->{fh}); |
255 | File::Sync::fsync($self->{fh}); |
|
|
256 | } |
|
|
257 | |
|
|
258 | $self->write_mdif; |
|
|
259 | |
310 | delete $self->{fh}; |
260 | delete $self->{fh}; |
|
|
261 | } |
|
|
262 | |
|
|
263 | sub check { |
|
|
264 | my $self = shift; |
|
|
265 | my $conf = $self->conf_path; |
|
|
266 | my $guard = $::lockdisk->guard; |
|
|
267 | |
|
|
268 | slog 3, "checking $self->{path}\n"; |
|
|
269 | |
|
|
270 | my ($fsize, $mtime) = (stat $self->{fh})[7, 9]; |
|
|
271 | |
|
|
272 | if ($self->{idx}) { |
|
|
273 | return 1 if $fsize == $self->{fsize} |
|
|
274 | && $mtime == $self->{mtime}; |
|
|
275 | } else { |
|
|
276 | if (open my $fh, "<", $conf) { |
|
|
277 | my %conf; |
|
|
278 | <$fh>; # skip initial comment |
|
|
279 | <$fh> eq "[SYNCMAIL]\n" |
|
|
280 | or die "$conf: format error"; |
|
|
281 | while (<$fh> =~ /^([a-z]+)\s*=\s*(.*)$/) { |
|
|
282 | $conf{$1} = $2; |
|
|
283 | } |
|
|
284 | return 1 if $fsize == $conf{fsize} |
|
|
285 | && $mtime == $conf{mtime}; |
|
|
286 | |
|
|
287 | $conf{mtime} <= $mtime |
|
|
288 | or die "$self->{path}: folder older than mdif"; |
|
|
289 | } |
|
|
290 | } |
|
|
291 | |
|
|
292 | slog 2, "updating $self->{path}\n"; |
|
|
293 | |
|
|
294 | my @idx; |
|
|
295 | |
|
|
296 | seek $self->{fh}, 0, SEEK_SET; |
|
|
297 | |
|
|
298 | parse_mbox $self->{fh}, sub { |
|
|
299 | my ($offs, $head, $body) = @_; |
|
|
300 | push @idx, [$offs, hash($$head, "\n\n", $$body)]; |
|
|
301 | } or die "$self->{path}: no valid mbox file"; |
|
|
302 | |
|
|
303 | $self->read_mdif; |
|
|
304 | |
|
|
305 | $self->{version} ||= MDIFVERSION; |
|
|
306 | $self->{ctime} = time; |
|
|
307 | |
|
|
308 | $self->gendiff($self->{idx}, \@idx); |
|
|
309 | |
|
|
310 | $self->{fsize} = $fsize; |
|
|
311 | $self->{mtime} = $mtime; |
|
|
312 | $self->{idx} = \@idx; |
|
|
313 | |
|
|
314 | $self->dirty; |
|
|
315 | $self->write_mdif;#d# |
|
|
316 | } |
|
|
317 | |
|
|
318 | sub inventory { |
|
|
319 | hash sort map { $_->[1] } @{$_[0]{idx}}; |
|
|
320 | } |
|
|
321 | |
|
|
322 | sub iidx { |
|
|
323 | my $self = shift; |
|
|
324 | |
|
|
325 | $self->{iidx} ||= do { |
|
|
326 | my %iidx; |
|
|
327 | my $idx = $self->{idx}; |
|
|
328 | |
|
|
329 | push @$idx, [$self->{fsize}]; |
|
|
330 | my $ofs = 0; |
|
|
331 | for (0 .. @$idx - 2) { |
|
|
332 | $iidx{$idx->[$_][1]} = [$idx->[$_][0], $idx->[$_+1][0] - $idx->[$_][0]]; |
|
|
333 | } |
|
|
334 | pop @$idx, [$self->{fsize}]; |
|
|
335 | |
|
|
336 | \%iidx; |
|
|
337 | }; |
|
|
338 | } |
|
|
339 | |
|
|
340 | sub exists { |
|
|
341 | $_[0]->iidx unless $_[0]{iidx}; |
|
|
342 | return $_[0]{iidx}{$_[1]}; |
|
|
343 | } |
|
|
344 | |
|
|
345 | sub fetch { |
|
|
346 | my ($self, $hash) = @_; |
|
|
347 | |
|
|
348 | $self->iidx unless $self->{iidx}; |
|
|
349 | |
|
|
350 | my $mail; |
|
|
351 | |
|
|
352 | my $msg = $self->{iidx}{$hash} |
|
|
353 | or die "$hash: no such message in $self->{path}"; |
|
|
354 | |
|
|
355 | print STDERR "$self->{fh}, $msg->[0], SEEK_SET\n";#d# |
|
|
356 | |
|
|
357 | seek $self->{fh}, $msg->[0], SEEK_SET |
|
|
358 | or die "$self->{path}: $!"; |
|
|
359 | |
|
|
360 | print STDERR "$msg->[1] == read $self->{fh}, $msg->[1]\n";#d# |
|
|
361 | |
|
|
362 | $msg->[1] == read $self->{fh}, $mail, $msg->[1] |
|
|
363 | or die "$self->{path}: $!"; |
|
|
364 | |
|
|
365 | $mail =~ /^From \S/ |
|
|
366 | or die "$self->{path}: mail folder corrupted"; |
|
|
367 | |
|
|
368 | $mail; |
311 | } |
369 | } |
312 | |
370 | |
313 | # begin updating folder |
371 | # begin updating folder |
314 | sub begin_update { |
372 | sub begin_update { |
315 | my $self = shift; |
373 | my $self = shift; |
316 | |
374 | |
317 | $self->{oidx} = $self->{idx}; |
375 | $self->{oidx} = $self->{idx}; |
318 | |
|
|
319 | } |
376 | } |
320 | |
377 | |
321 | sub delete { |
378 | sub delete { |
322 | my $self = shift; |
379 | my $self = shift; |
323 | my $temp = "$self->{path}~"; |
380 | my $temp = "$self->{path}~"; |
324 | |
381 | |
|
|
382 | $self->iidx unless $self->{iidx}; |
|
|
383 | |
325 | if (@_) { |
384 | for (@_) { |
|
|
385 | if (exists $self->{iidx}{$_}) { # at least one message exists |
326 | my $guard = $::lockdisk->guard; |
386 | my $guard = $::lockdisk->guard; |
327 | my %del; @del{@_} = (); |
387 | my %del; @del{@_} = (); |
|
|
388 | my @nidx; |
|
|
389 | my $dofs = 0; |
328 | |
390 | |
329 | open my $fh, ">", $temp |
391 | open my $fh, "+>", $temp |
330 | or die "$temp: $!"; |
392 | or die "$temp: $!"; |
331 | |
393 | |
332 | my $nidx; |
394 | eval { |
333 | my $idx = delete $self->{idx}; |
395 | 0 == setlkw(fileno $fh, 2) |
334 | push @$idx, [$self->{fsize}]; |
|
|
335 | $self->{fsize} = 0; # we virtually truncated the file |
|
|
336 | |
|
|
337 | slog 0, "XXXXXXXXXXXXXXX @_\n";#d# |
|
|
338 | |
|
|
339 | my $ofs = 0; |
|
|
340 | for (0 .. @$idx - 2) { |
|
|
341 | my $buf; |
|
|
342 | |
|
|
343 | unless (exists $del{$idx->[$_][1]}) { |
|
|
344 | my $len = $idx->[$_+1][0] - $idx->[$_][0]; |
|
|
345 | |
|
|
346 | slog 0, "$idx->[$_][1] $idx->[$_+1][0] - $idx->[$_][0]\n";#d# |
|
|
347 | |
|
|
348 | seek $self->{fh}, $idx->[$_][0],SEEK_SET |
|
|
349 | or die "$self->{path}: $!"; |
396 | or die "$self->{path}~: $!"; |
350 | |
397 | |
|
|
398 | $self->{fsize} = 0; # we virtually truncated the file |
|
|
399 | |
|
|
400 | for (@{delete $self->{idx}}) { |
|
|
401 | my $hash = $_->[1]; |
|
|
402 | my $buf; |
|
|
403 | |
|
|
404 | unless (exists $del{$hash}) { |
|
|
405 | my ($ofs, $len) = @{$self->{iidx}{$hash}}; |
|
|
406 | |
|
|
407 | $len or die; |
|
|
408 | |
|
|
409 | seek $self->{fh}, $ofs, SEEK_SET |
|
|
410 | or die "$self->{path}: $!"; |
|
|
411 | |
351 | $len == read $self->{fh}, $buf, $len |
412 | $len == read $self->{fh}, $buf, $len |
352 | or die "$self->{path}: $!"; |
413 | or die "$self->{path}: $!"; |
353 | |
414 | |
354 | $buf =~ /^From \S/ |
415 | $buf =~ /^From \S/ |
355 | or die "$self->{path}: corrupted mail folder"; |
416 | or die "$self->{path}: corrupted mail folder"; |
356 | |
417 | |
|
|
418 | print $fh $buf |
|
|
419 | or die "$self->{path}: $!"; |
|
|
420 | |
|
|
421 | push @nidx, [$dofs, $hash]; |
|
|
422 | $self->{iidx}{$hash}[0] = $dofs; |
|
|
423 | $dofs += $len; |
|
|
424 | |
357 | &::give unless ++$ecnt & 255; |
425 | &::give unless ++$ecnt & 255; |
358 | } else { |
426 | } else { |
359 | slog 0, "skipping $idx->[$_][1]\n"; |
427 | delete $self->{iidx}{$hash}; |
|
|
428 | slog 0, "skipping/deleting $hash\n"; |
|
|
429 | } |
|
|
430 | } |
|
|
431 | }; |
|
|
432 | |
|
|
433 | if ($@) { |
|
|
434 | close $fh; |
|
|
435 | unlink $temp; |
|
|
436 | die; |
360 | } |
437 | } |
|
|
438 | |
|
|
439 | fflush($fh); |
|
|
440 | File::Sync::fsync($fh); |
|
|
441 | replace $temp, $self->{path}; |
|
|
442 | |
|
|
443 | $self->{fh} = $fh; |
|
|
444 | $self->{rw} = 1; |
|
|
445 | |
|
|
446 | delete $self->{iidx}; |
|
|
447 | |
|
|
448 | $self->{idx} = \@nidx; |
|
|
449 | $self->{fsize} = $dofs; |
|
|
450 | |
|
|
451 | return; |
361 | } |
452 | } |
|
|
453 | } |
|
|
454 | } |
362 | |
455 | |
363 | File::Sync::fsync($fh); |
456 | sub append { |
364 | close $fh; |
457 | my ($self, $hash, $mail) = @_; |
365 | |
458 | |
366 | # replace $temp, $self->{path} |
459 | if (length $mail) { |
|
|
460 | $self->open(1); |
|
|
461 | |
|
|
462 | seek $self->{fh}, $self->{fsize}, SEEK_SET |
|
|
463 | or die "$self->{path}: $!"; |
|
|
464 | |
|
|
465 | print {$self->{fh}} $mail |
|
|
466 | or die "$self->{path}: $!"; |
|
|
467 | |
|
|
468 | push @{$self->{idx}}, [$self->{fsize}, $hash]; |
|
|
469 | $self->{fsize} += length $mail; |
367 | } |
470 | } |
368 | } |
471 | } |
369 | |
472 | |
370 | sub end_update { |
473 | sub end_update { |
371 | } |
|
|
372 | |
|
|
373 | sub append { |
|
|
374 | my $self = shift; |
474 | my $self = shift; |
375 | |
475 | |
376 | &update; |
476 | $self->gendiff((delete $self->{oidx}), $self->{idx}); |
377 | #$self->open(1); |
477 | |
|
|
478 | fflush($self->{fh}); |
|
|
479 | File::Sync::fsync($self->{fh}); |
|
|
480 | |
|
|
481 | stat $self->{fh} |
|
|
482 | or die "$self->{path}: $!"; |
|
|
483 | |
|
|
484 | $self->{fsize} = (stat _)[7]; |
|
|
485 | $self->{mtime} = (stat _)[9]; |
|
|
486 | |
|
|
487 | $self->dirty; |
378 | } |
488 | } |
379 | |
489 | |
380 | 1; |
490 | 1; |
381 | |
491 | |
382 | __DATA__ |
492 | __DATA__ |