… | |
… | |
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 | flushfh($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 | flushfh($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 | seek $self->{fh}, $msg->[0], SEEK_SET |
|
|
356 | or die "$self->{path}: $!"; |
|
|
357 | |
|
|
358 | $msg->[1] == read $self->{fh}, $mail, $msg->[1] |
|
|
359 | or die "$self->{path}: $!"; |
|
|
360 | |
|
|
361 | $mail =~ /^From \S/ |
|
|
362 | or die "$self->{path}: mail folder corrupted"; |
|
|
363 | |
|
|
364 | $mail; |
311 | } |
365 | } |
312 | |
366 | |
313 | # begin updating folder |
367 | # begin updating folder |
314 | sub begin_update { |
368 | sub begin_update { |
315 | my $self = shift; |
369 | my $self = shift; |
316 | |
370 | |
317 | $self->{oidx} = $self->{idx}; |
371 | $self->{oidx} = $self->{idx}; |
318 | |
|
|
319 | } |
372 | } |
320 | |
373 | |
321 | sub delete { |
374 | sub delete { |
322 | my $self = shift; |
375 | my $self = shift; |
323 | my $temp = "$self->{path}~"; |
376 | my $temp = "$self->{path}~"; |
324 | |
377 | |
|
|
378 | $self->iidx unless $self->{iidx}; |
|
|
379 | |
325 | if (@_) { |
380 | for (@_) { |
|
|
381 | if (exists $self->{iidx}{$_}) { # at least one message exists |
326 | my $guard = $::lockdisk->guard; |
382 | my $guard = $::lockdisk->guard; |
327 | my %del; @del{@_} = (); |
383 | my %del; @del{@_} = (); |
|
|
384 | my @nidx; |
328 | |
385 | |
329 | open my $fh, ">", $temp |
386 | open my $fh, ">", $temp |
330 | or die "$temp: $!"; |
387 | or die "$temp: $!"; |
331 | |
388 | |
332 | my $nidx; |
389 | eval { |
333 | my $idx = delete $self->{idx}; |
390 | 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}: $!"; |
391 | or die "$self->{path}~: $!"; |
350 | |
392 | |
|
|
393 | $self->{fsize} = 0; # we virtually truncated the file |
|
|
394 | |
|
|
395 | my $dofs = 0; |
|
|
396 | for (@{delete $self->{idx}}) { |
|
|
397 | my $hash = $_->[1]; |
|
|
398 | my $buf; |
|
|
399 | |
|
|
400 | unless (exists $del{$hash}) { |
|
|
401 | my ($ofs, $len) = @{$self->{iidx}{$hash}}; |
|
|
402 | |
|
|
403 | $len or die; |
|
|
404 | |
|
|
405 | seek $self->{fh}, $ofs, SEEK_SET |
|
|
406 | or die "$self->{path}: $!"; |
|
|
407 | |
351 | $len == read $self->{fh}, $buf, $len |
408 | $len == read $self->{fh}, $buf, $len |
352 | or die "$self->{path}: $!"; |
409 | or die "$self->{path}: $!"; |
353 | |
410 | |
354 | $buf =~ /^From \S/ |
411 | $buf =~ /^From \S/ |
355 | or die "$self->{path}: corrupted mail folder"; |
412 | or die "$self->{path}: corrupted mail folder"; |
356 | |
413 | |
|
|
414 | print $fh $buf |
|
|
415 | or die "$self->{path}: $!"; |
|
|
416 | |
|
|
417 | push @nidx, [$dofs, $hash]; |
|
|
418 | $self->{iidx}{$hash}[0] = $dofs; |
|
|
419 | $dofs += $len; |
|
|
420 | |
357 | &::give unless ++$ecnt & 255; |
421 | &::give unless ++$ecnt & 255; |
358 | } else { |
422 | } else { |
359 | slog 0, "skipping $idx->[$_][1]\n"; |
423 | delete $self->{iidx}{$hash}; |
|
|
424 | slog 0, "skipping/deleting $hash\n"; |
|
|
425 | } |
|
|
426 | } |
|
|
427 | }; |
|
|
428 | |
|
|
429 | if ($@) { |
|
|
430 | close $fh; |
|
|
431 | unlink $temp; |
|
|
432 | die; |
360 | } |
433 | } |
|
|
434 | |
|
|
435 | File::Sync::fsync($fh); |
|
|
436 | replace $temp, $self->{path}; |
|
|
437 | |
|
|
438 | $self->{fh} = $fh; |
|
|
439 | $self->{rw} = 1; |
|
|
440 | |
|
|
441 | delete $self->{iidx}; |
|
|
442 | |
|
|
443 | $self->{idx} = \@nidx; |
|
|
444 | $self->{fsize} = $ofs; |
|
|
445 | |
|
|
446 | return; |
361 | } |
447 | } |
|
|
448 | } |
|
|
449 | } |
362 | |
450 | |
363 | File::Sync::fsync($fh); |
451 | sub append { |
364 | close $fh; |
452 | my ($self, $hash, $mail) = @_; |
365 | |
453 | |
366 | # replace $temp, $self->{path} |
454 | if (length $mail) { |
|
|
455 | $self->open(1); |
|
|
456 | |
|
|
457 | seek $self->{fh}, $self->{fsize}, SEEK_SET |
|
|
458 | or die "$self->{path}: $!"; |
|
|
459 | |
|
|
460 | print {$self->{fh}} $mail |
|
|
461 | or die "$self->{path}: $!"; |
|
|
462 | |
|
|
463 | push @{$self->{idx}}, [$self->{fsize}, $hash]; |
|
|
464 | $self->{fsize} += length $mail; |
367 | } |
465 | } |
368 | } |
466 | } |
369 | |
467 | |
370 | sub end_update { |
468 | sub end_update { |
371 | } |
|
|
372 | |
|
|
373 | sub append { |
|
|
374 | my $self = shift; |
469 | my $self = shift; |
375 | |
470 | |
376 | &update; |
471 | $self->gendiff((delete $self->{oidx}, $self->{idx})); |
377 | #$self->open(1); |
472 | |
|
|
473 | flushfh($self->{fh}); |
|
|
474 | File::Sync::fsync($self->{fh}); |
|
|
475 | |
|
|
476 | stat $self->{fh} |
|
|
477 | or die "$self->{path}: $!"; |
|
|
478 | |
|
|
479 | $self->{fsize} = (stat _)[7]; |
|
|
480 | $self->{mtime} = (stat _)[9]; |
|
|
481 | |
|
|
482 | $self->dirty; |
378 | } |
483 | } |
379 | |
484 | |
380 | 1; |
485 | 1; |
381 | |
486 | |
382 | __DATA__ |
487 | __DATA__ |