pjs/grendel/storage/MailSummaryFile.java

357 строки
13 KiB
Java

/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Grendel mail/news client.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are Copyright (C) 1997
* Netscape Communications Corporation. All Rights Reserved.
*
* Created: Jamie Zawinski <jwz@netscape.com>, 28 Sep 1997.
*/
package grendel.storage;
import calypso.util.Assert;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.io.EOFException;
import java.util.Hashtable;
import java.util.Enumeration;
abstract class MailSummaryFile {
/** The folder and associated disk file. The file might not exist. */
protected BerkeleyFolder folder;
protected File file; // note this is the *folder*, not the *summary*.
/* This holds the date of the file <I>when it was parsed.</I>
We need to save this (older) values so that we can tell when
the summary file is out of date. This is measured in the units
of File.lastModified(). */
protected long file_date;
/* This holds the size of the file <I>when it was parsed.</I>
We need to save this (older) values so that we can tell when
the summary file is out of date. */
protected long file_size;
/* The total number of messages, non-deleted messages, and unread messages
in the folder, assuming that file_date and file_size match reality. */
protected int total_message_count = -1;
protected int undeleted_message_count = -1;
protected int unread_message_count = -1;
protected long deleted_message_bytes = -1;
/** For salvageMessages(). If the table is non-null, its keys will be
MessageID objects, and the values will be opaque objects intended
to be passed to salvageMessageSummary(). Subclasses are responsible
for populating this table, if they are able.
*/
protected Hashtable salvage = null;
MailSummaryFile(BerkeleyFolder f) {
folder = f;
file = folder.getFile();
file_size = file.length();
file_date = file.lastModified();
}
/** Parses a summary file for the associated folder, and adds the described
message objects to the folder. The given stream is assumed to have
been opened, and positioned past any version-identifying header info.
Returns the byte position at which parsing should resume (that is, the
last byte position which the summary file summarizes.)
*/
abstract long readSummaryFile(InputStream sum) throws IOException;
/** Whether this class knows how to write a summary file. */
boolean writable() { return false; }
/** Write a summary file for the associated folder. If a subclass provides
an implementation for this method, it should also override writable().
*/
void writeSummaryFile() throws IOException {
throw new Error("unimplemented");
}
/** Returns the date that the summary file expects to find on the
folder's file. This is measured in the units of File.lastModified().
Set this expectation with setFolderDateAndSize().
*/
long folderDate() {
return file_date;
}
/** Returns the size in bytes that the summary file expects of the
folder's file. Set this expectation with setFolderDateAndSize().
*/
long folderSize() {
return file_size;
}
/** Returns the total number of messages in the folder.
This includes deleted and unread messages.
*/
int totalMessageCount() {
if (total_message_count == -1)
synchronized (this) {
if (total_message_count == -1) // double check
getMessageCounts();
}
return total_message_count;
}
/** Returns the number of non-deleted messages in the folder.
This includes unread messages.
*/
int undeletedMessageCount() {
if (undeleted_message_count == -1)
synchronized (this) {
if (undeleted_message_count == -1) // double check
getMessageCounts();
}
return undeleted_message_count;
}
/** Returns the number of unread messages in the folder.
This does not include unread messages that are also deleted.
*/
int unreadMessageCount() {
if (unread_message_count == -1)
synchronized (this) {
if (unread_message_count == -1) // double check
getMessageCounts();
}
return unread_message_count;
}
/** Returns the number of bytes consumed by deleted but not expunged
messages in the folder. */
long deletedMessageBytes() {
if (deleted_message_bytes == -1)
synchronized (this) {
if (deleted_message_bytes == -1) // double check
getMessageCounts();
}
return deleted_message_bytes;
}
/** Read only enough of the file to fill in the values of
total_message_count, undeleted_message_count, unread_message_count,
and deleted_message_bytes. */
abstract protected void getMessageCounts();
/** Set the date and size of the corresponding folder-file.
This date/time pair should correspond to the file at the time at
which it was <I>parsed</I>, not the current time.
The next time writeSummaryFile() is called, the new summary
file will encode this date and size expectation.
*/
void setFolderDateAndSize(long file_date, long file_size) {
this.file_date = file_date;
this.file_size = file_size;
}
/** Set the message counts (total, undeleted, and unread) of the
corresponding folder-file. The next time writeSummaryFile() is
called, the new summary file will include these numbers.
@param total_count The total number of messages in the folder.
This must match the number of elements returned
by getMessages() at the time that
writeSummaryFile() is called.
@param undeleted_count The total number of messages in the folder
that are not marked as deleted. This also
must match reality when writeSummaryFile() is
called.
@param unread_count The total number of messages in the folder
that are not marked as either read or deleted.
Ditto writeSummaryFile().
@param deleted_bytes The total number of bytes consumed by messages
that are marked as deleted.
Ditto writeSummaryFile().
*/
void setFolderMessageCounts(int total_count, int undeleted_count,
int unread_count, long deleted_bytes) {
Assert.Assertion(total_count >= 0);
Assert.Assertion(undeleted_count >= 0);
Assert.Assertion(unread_count >= 0);
Assert.Assertion(deleted_bytes >= 0);
Assert.Assertion(undeleted_count <= total_count);
Assert.Assertion(unread_count <= total_count);
this.total_message_count = total_count;
this.undeleted_message_count = undeleted_count;
this.unread_message_count = unread_count;
this.deleted_message_bytes = deleted_bytes;
}
/** Called when the folder's disk file has been appended to by *this*
program. (As opposed to, an unexpected, unknown change by some other
program.) This calls setFolderDateAndSize() with the current date
and size of the folder file (as it appears on disk right now.)
<P> If the number of messages has changed, you should have called
setFolderMessageCounts() before calling this.
*/
void updateSummaryFile() throws IOException {
setFolderDateAndSize(file.lastModified(), file.length());
// by default, do nothing to the disk file.
}
/** Compare the date of the folder disk file to a date (presumably) read
from the summary file, and return true of they are equal.
*/
protected boolean checkFolderDate(long summary_date) {
long folder_date = file.lastModified();
// #### System dependency: to be compatible with the Cheddar file format,
// we need to be able to compare lastModified() with the time_t
// value in the file (which came from `stat.st_mtime'.)
//
// Note that, to be compatible with this, MailSummaryFileGrendel divides
// the date by 1000 when saving it to the file.
//
summary_date *= 1000L;
return (folder_date == summary_date);
}
/** Compare the size of the folder disk file to a size (presumably) read
from the summary file, and return true of they are equal.
*/
protected boolean checkFolderSize(long summary_size) {
return (summary_size == file.length());
}
/** Call this if something has gone wrong when parsing the summary file,
but <I>after</I> the folder file has been parsed. This might cause
some additional information to be scavenged from the summary file
and stored into the messages.
<P> The idea here is that an out-of-date summary file cannot be
trusted as far as byte-offsets and such goes, but if the summary
file had stored some property of a message (such as whether it was
deleted) then we might as well believe that, lacking any other
source of information.
<P> Note that one folder can contain multiple messages with the same
ID, so this process is not totally foolproof. It is, however,
better than nothing.
*/
void salvageMessages() {
if (salvage == null) return;
Hashtable t = salvage;
salvage = null;
if (folder.fMessages != null) {
for (Enumeration e = folder.fMessages.elements(); e.hasMoreElements();) {
BerkeleyMessage m = (BerkeleyMessage) e.nextElement();
MessageID id = (MessageID) m.getMessageID();
if (id == null) continue;
Object value = t.get(id);
if (value != null)
salvageMessage(m, value);
}
}
}
protected void salvageMessage(BerkeleyMessage m, Object salvage_object) {
// default is to do nothing. Subclasses should implement this if
// they can.
}
/* Some random I/O utilities... */
protected synchronized int readUnsignedShort(InputStream s)
throws IOException {
int b0 = s.read(); if (b0 == -1) throw new EOFException();
int b1 = s.read(); if (b1 == -1) throw new EOFException();
return ((b0 << 8) | b1);
}
protected synchronized long readUnsignedInteger(InputStream s)
throws IOException {
int b0 = s.read(); if (b0 == -1) throw new EOFException();
int b1 = s.read(); if (b1 == -1) throw new EOFException();
int b2 = s.read(); if (b2 == -1) throw new EOFException();
int b3 = s.read(); if (b3 == -1) throw new EOFException();
return ((((long)b0) << 24) | (b1 << 16) | (b2 << 8) | b3);
}
protected synchronized void writeUnsignedShort(OutputStream s, int i)
throws IOException {
s.write((i >> 8) & 0xFF);
s.write(i & 0xFF);
}
protected synchronized void writeUnsignedInteger(OutputStream s, long i)
throws IOException {
s.write((int) (i >> 24) & 0xFF);
s.write((int) (i >> 16) & 0xFF);
s.write((int) (i >> 8) & 0xFF);
s.write((int) (i & 0xFF));
}
/* Java sucks! I had to duplicate these because, as far as I can tell,
there is no class or interface which FileInputStream and RandomAccessFile
have in common, nor that FileOutputStream and RandomAccessFile have in
common. What a total crock!
*/
protected synchronized int readUnsignedShort(RandomAccessFile s)
throws IOException {
int b0 = s.read(); if (b0 == -1) throw new EOFException();
int b1 = s.read(); if (b1 == -1) throw new EOFException();
return ((b0 << 8) | b1);
}
protected synchronized long readUnsignedInteger(RandomAccessFile s)
throws IOException {
int b0 = s.read(); if (b0 == -1) throw new EOFException();
int b1 = s.read(); if (b1 == -1) throw new EOFException();
int b2 = s.read(); if (b2 == -1) throw new EOFException();
int b3 = s.read(); if (b3 == -1) throw new EOFException();
return ((((long)b0) << 24) | (b1 << 16) | (b2 << 8) | b3);
}
protected synchronized void writeUnsignedShort(RandomAccessFile s, int i)
throws IOException {
s.write((i >> 8) & 0xFF);
s.write(i & 0xFF);
}
protected synchronized void writeUnsignedInteger(RandomAccessFile s, long i)
throws IOException {
s.write((int) (i >> 24) & 0xFF);
s.write((int) (i >> 16) & 0xFF);
s.write((int) (i >> 8) & 0xFF);
s.write((int) (i & 0xFF));
}
}