gecko-dev/grendel/storage/ParseBerkeleyFolderAndExpunge.java

287 lines
9.1 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>, 2 Oct 1997.
*/
package grendel.storage;
import calypso.util.Assert;
import calypso.util.ByteBuf;
import javax.mail.internet.InternetHeaders;
import javax.mail.Header;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.io.EOFException;
import java.util.Enumeration;
import grendel.util.Constants;
class ParseBerkeleyFolderAndExpunge extends ParseBerkeleyFolder {
private static final boolean DEBUG = true;
protected RandomAccessFile in = null;
protected RandomAccessFile out = null;
protected byte bytes[] = null;
protected ByteBuf bytebuf = null;
protected byte blank_mozilla_status[] = null;
protected byte[] linebreak = null;
Enumeration msgs = null;
synchronized void expungeDeletedMessages(BerkeleyFolder f,
RandomAccessFile in,
RandomAccessFile out,
Enumeration old_msgs)
throws IOException {
Assert.Assertion(old_msgs != null);
total_message_count = 0; // recordOne() updates these.
undeleted_message_count = 0;
unread_message_count = 0;
deleted_message_bytes = 0;
this.in = in;
this.out = out;
bytes = new byte[1024 * 4];
bytebuf = new ByteBuf();
blank_mozilla_status = ("X-Mozilla-Status: 0000" +
Constants.LINEBREAK).getBytes();
// Figure out what line terminator is used in this file by searching
// forward for the first "\n", "\r", or "\r\n".
{
long pos = in.getFilePointer();
int b;
do {
b = in.read();
} while (b != -1 && b != '\r' && b != '\n');
if (b == -1 || (b == '\r' && (in.read() == '\n')))
linebreak = "\r\n".getBytes();
else if (b == '\n')
linebreak = "\n".getBytes();
else
linebreak = "\r".getBytes();
in.seek(pos);
}
msgs = old_msgs;
mapOverMessages(in, f);
bytes = null;
bytebuf = null;
linebreak = null;
}
void recordOne(int envelope_start, int message_start,
int headers_end, int message_end,
InternetHeaders headers)
throws IOException {
// System.err.println(" ** " +
// envelope_start + " " + message_start + " " +
// headers_end + " " + message_end);
Assert.Assertion(envelope_start >= 0 &&
message_start > envelope_start &&
headers_end >= message_start &&
message_end >= headers_end);
BerkeleyMessage old_msg = (BerkeleyMessage) msgs.nextElement();
Assert.Assertion(old_msg != null);
if (DEBUG) {
// Make sure the sequence of messages in the file is in step with the
// sequence of messages in the folder object.
String idh = null;
String hh[] = headers.getHeader("Message-ID");
if (hh != null && hh.length != 0)
idh = hh[0];
if (idh != null) {
MessageID id = new MessageID(idh);
if (bytebuf.length() > 4 &&
!id.equals(old_msg.getMessageID())) {
System.err.println("ids differ!");
System.err.println(" new id = " + id + " (" + idh + ")");
System.err.println(" old id = " + old_msg.getMessageID());
}
}
}
if ((old_msg.flags & MessageBase.FLAG_DELETED) != 0)
// This message is deleted; don't write it out.
return;
// Create a new message. We can't reuse the old message, because the
// caller is going to want to back out of any changes if an error occurs;
// so it needs to hold on to a copy of the original data until we're done.
//
BerkeleyMessage new_msg = new BerkeleyMessage(fFolder, headers);
// First, copy the envelope line from one file to the other.
// Then remember the position in the output file of (what will be) the
// beginning of the headers of this message.
//
long in_pos = in.getFilePointer();
in.seek(envelope_start);
copyBytes(message_start - envelope_start, false);
new_msg.setStorageFolderIndex((int) out.getFilePointer());
int total_msg_bytes_written = 0; // doesn't include envelope
// Now, write out the headers. Rather than just copying the bytes of
// the headers from one file to another, we reconstruct the output
// headers from the parsed headers object. We do this because it's
// the easiest way to skip over (don't copy) certain headers, which
// we want to recompute the values of.
//
// Note that this could possibly (but not probably) change the linebreaks
// of the headers during their passage from one file to another. Note
// also that we *don't* change the linebreaks of the bodies, just the
// headers. Since all linebreaks in one file should match, this
// shouldn't happen (or matter.)
//
for (Enumeration e = headers.getAllHeaders(); e.hasMoreElements();) {
Header hh = (Header) e.nextElement();
String h = hh.getName();
String v = hh.getValue();
// Don't copy these headers from input to output.
if (h.equalsIgnoreCase("X-Mozilla-Status") ||
h.equalsIgnoreCase("Content-Length") ||
h.equalsIgnoreCase("X-UIDL"))
continue;
bytebuf.setLength(0);
bytebuf.append(h);
bytebuf.append(": ");
bytebuf.append(v);
bytebuf.append(linebreak);
out.write(bytebuf.toBytes(), 0, bytebuf.length());
total_msg_bytes_written += bytebuf.length();
}
// Now, write out the recomputed values of the X-Mozilla-Status,
// Content-Length, and (maybe) X-UIDL headers.
//
bytebuf.setLength(0);
if (false) {
bytebuf.append("X-UIDL: ");
bytebuf.append("####");
bytebuf.append(linebreak);
}
// if (old_msg.flags != new_msg.flags) {
// System.err.println("flags diff at " + message_start + ": " +
// old_msg.flags + " " + new_msg.flags);
// }
long xms = BerkeleyMessage.makeMozillaFlags(old_msg);
bytebuf.append("X-Mozilla-Status: ");
String s = Long.toHexString(xms);
int i = s.length();
if (i == 1) bytebuf.append("000");
else if (i == 2) bytebuf.append("00");
else if (i == 3) bytebuf.append("0");
bytebuf.append(s);
bytebuf.append(linebreak);
bytebuf.append("Content-Length: ");
int cl = (message_end - headers_end - linebreak.length);
if (cl < 0) cl = 0;
bytebuf.append(cl);
bytebuf.append(linebreak);
out.write(bytebuf.toBytes(), 0, bytebuf.length());
total_msg_bytes_written += bytebuf.length();
// We've written the headers. Now copy the body bytes.
//
in.seek(headers_end);
copyBytes(message_end - headers_end, true);
total_msg_bytes_written += (message_end - headers_end);
in.seek(in_pos);
// Now that we've written the data, update the message with the true
// byte-length (this will be the value of the Content-Length header,
// plus the number of bytes we wrote for the headers.)
//
new_msg.setSize(total_msg_bytes_written);
// Update the summary counts...
Assert.Assertion(!new_msg.isDeleted());
total_message_count++;
undeleted_message_count++;
if (!new_msg.isRead())
unread_message_count++;
// Now hand this new message object back to the folder, and we're done.
//
fFolder.noticeInitialMessage(new_msg);
}
/** Copy N bytes from `in' to `out'.
If `blank_line' is true, and the data copied didn't end with a
linebreak, insert a linebreak.
*/
private void copyBytes(int n, boolean blank_line) throws IOException {
int buf_length = bytes.length;
int bytes_left = n;
boolean broken = false;
while (bytes_left > 0) {
int to_read = bytes_left;
if (to_read > buf_length)
to_read = buf_length;
int bytes_read = in.read(bytes, 0, to_read);
// System.err.println(" read " + bytes_read +
// " (with " + bytes_left + " left)");
if (bytes_read < 0)
throw new EOFException();
else
bytes_left -= bytes_read;
out.write(bytes, 0, bytes_read);
if (blank_line)
broken = (bytes[bytes_read-1] == '\r' ||
bytes[bytes_read-1] == '\n');
}
if (blank_line && !broken) {
// System.err.println("inventing linebreak at " + in.getFilePointer());
out.write(linebreak, 0, linebreak.length);
}
}
}