зеркало из https://github.com/mozilla/pjs.git
357 строки
13 KiB
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));
|
|
}
|
|
|
|
}
|