/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (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. * * Contributor(s): * * Created: Jamie Zawinski , 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); } } }