classpathx-javamail
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Classpathx-javamail] GnU mBox


From: Chris
Subject: Re: [Classpathx-javamail] GnU mBox
Date: Thu, 03 Feb 2005 15:20:11 +1100
User-agent: Mozilla Thunderbird 1.0 (Windows/20041206)


Attached is a new version of MboxStore and MboxFolder to implement what I have been talking about. What should happen now is that name separator for Mbox is "/" on all platforms (just like URLs). As per the MboxStore specification, names that start with "/" are absolute and names not starting with "/" are relative to the root that was passed to MboxStore. As a concession to Windows users "//" at the beginning is removed, so at //c:/foo -> c:/foo.

The inbox handling has changed. It only is activated AFTER checking that there isn't an INBOX relative to the root. I'm not clear on the benefit of this feature, but it remains in there.

If those in charge here have any problem with this idea, let me know what the issue is and I'll see what I can do.


Nic Ferrier wrote:

Countach <address@hidden> writes:

Further to my email of yesterday about folder names, I noticed this in the java spec for Store class....

name - The name of the Folder.
In some Stores, name can be an absolute path if it starts with the hierarchy delimiter.
Else it is interpreted relative to the 'root' of this namespace.

This suggests to me that gnu mbox is not conforming as it is. It's not clear what the "hierarchy delimiter" is of mbox, but on windows at least it would appear to be the file name delimiter - backslash \\. That being so, any string passed to getFolder that doesn't start with backslash should be interpreted relative to the root of the namespace. Since windows pathnames start with a drive letter, that means passing an absolute file name in windows should be interpreted relative. Thus mbox is non-conforming.

That to me is further evidence that mbox names shouldn't be the file name but rather the relative name. Anybody disagree?

Patches are always welcome.



/*
 * MboxStore.java
 * Copyright(C) 1999 Chris Burdess <address@hidden>
 *
 * This file is part of GNU JavaMail, a library.
 *
 * GNU JavaMail is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 *(at your option) any later version.
 *
 * GNU JavaMail is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * As a special exception, if you link this library with other files to
 * produce an executable, this library does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * This exception does not however invalidate any other reasons why the
 * executable file might be covered by the GNU General Public License.
 */

package gnu.mail.providers.mbox;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;

import gnu.mail.treeutil.StatusEvent;
import gnu.mail.treeutil.StatusListener;
import gnu.mail.treeutil.StatusSource;

import gnu.inet.util.Logger;
import java.io.IOException;

/**
 * The storage class implementing the Mbox mailbox file format.
 *
 * @author <a href='mailto:address@hidden'>Chris Burdess</a>
 */
public final class MboxStore
  extends Store
  implements StatusSource
{

  private static final char separatorChar = '/';
  private static final String INBOX = "/inbox";

  static boolean attemptFallback = true;

  private List statusListeners = new ArrayList();

  /**
   * Constructor.
   */
  public MboxStore(Session session, URLName urlname)
  {
    super(session, urlname);
    String af = session.getProperty("mail.mbox.attemptfallback");
    if (af != null)
      {
        attemptFallback = Boolean.valueOf(af).booleanValue();
      }
  }

  /**
   * There isn't a protocol to implement, so this method just returns.
   */
  protected boolean protocolConnect(String host,
                                    int port,
                                    String username,
                                    String password)
    throws MessagingException
  {
    return true;
  }
  public Folder getDefaultFolder()
    throws MessagingException
  {
    return getFolder("");
  }

  /**
   * Returns the default folder.
   */
  public File getMailRootDir()
  {
    // If the url used to contruct the store references a file directly,
    // return this file.
    if (url!=null)
      {
        String file = url.getFile();
        if (file != null && file.length() > 0)
          {
            return new File(file);
          }
      }
    // Otherwise attempt to return a sensible root folder.
    String mailhome = session.getProperty("mail.mbox.mailhome");
    if (mailhome == null)
      {
        try
          {
            String userhome = System.getProperty("user.home");
            mailhome = userhome + "/Mail"; // elm
            if (!exists(mailhome))
              {
                mailhome = userhome + "/mail";
              }
            if (!exists(mailhome))
              {
                mailhome = null;
              }
          }
        catch (SecurityException e)
          {
            log("access denied reading system properties");
            mailhome = "/";
          }
      }
    return new File(mailhome);
  }




  /**
   * Returns the folder with the specified filename.
   */
  public Folder getFolder(String name)
    throws MessagingException
  {
    if (name == null)
      {
        name = "";
      }
    boolean inbox = false;
    if ((0 < name.length() && name.charAt(0) == '/') && !new 
File(name).exists() && INBOX.equalsIgnoreCase(name))
      {
        // First try the session property mail.mbox.inbox.
        String inboxname = session.getProperty("mail.mbox.inbox");
        if (!exists(inboxname))
          {
            inboxname = null;
          }
        if (inboxname == null && attemptFallback)
          {
            // Try some common(UNIX) locations.
            try
              {
                String username = System.getProperty("user.name");
                inboxname = "/var/mail/" + username; // GNU
                if (!exists(inboxname))
                  {
                    inboxname = "/var/spool/mail/" + username;
                    // common alternative
                  }
                if (!exists(inboxname))
                  {
                    inboxname = null;
                    String userhome = System.getProperty("user.home");
                    inboxname = userhome + "/Mailbox"; // qmail etc
                  }
                if (!exists(inboxname))
                  {
                    inboxname = null;
                  }
              }
            catch (SecurityException e)
              {
                // not allowed to read system properties
                log("unable to access system properties");
              }
          }
        if (inboxname!=null)
          {
            name = inboxname;
            inbox = true;
          }
        // otherwise we assume it is actually a file called "inbox"
      }
    return new MboxFolder(this, name, inbox);
//return new MboxFolder(this, name, false);
  }

  /*
   * Indicates whether the file referred to by the specified filename exists.
   */
  private boolean exists(String filename)
  {
    if (filename!=null)
      {
        File file = new File(filename);
        if (separatorChar != File.separatorChar)
          {
            file = new File(filename.replace(separatorChar,
                                             File.separatorChar));
          }
        return file.exists();
      }
    return false;
  }

  /**
   * Returns the folder specified by the filename of the URLName.
   */
  public Folder getFolder(URLName urlname)
    throws MessagingException
  {
    return getFolder(urlname.getFile());
  }

  Session getSession()
  {
    return session;
  }

  /**
   * Print a log message.
   */
  void log(String message)
  {
    if (session.getDebug())
      {
        Logger logger = Logger.getInstance();
        logger.log("mbox", message);
      }
  }

  // -- StatusSource --

  /**
   * Adds a status listener to this store.
   * The listener will be informed of state changes during potentially
   * lengthy procedures(opening and closing mboxes).
   * @param l the status listener
   * @see #removeStatusListener
   */
  public void addStatusListener(StatusListener l)
  {
    synchronized (statusListeners)
      {
        statusListeners.add(l);
      }
  }

  /**
   * Removes a status listener from this store.
   * @param l the status listener
   * @see #addStatusListener
   */
  public void removeStatusListener(StatusListener l)
  {
    synchronized (statusListeners)
      {
        statusListeners.remove(l);
      }
  }

  /**
   * Processes a status event.
   * This dispatches the event to all the registered listeners.
   * @param event the status event
   */
  protected void processStatusEvent(StatusEvent event)
  {
    StatusListener[] listeners;
    synchronized (statusListeners)
      {
        listeners = new StatusListener[statusListeners.size()];
        statusListeners.toArray(listeners);
      }
    switch (event.getType())
      {
      case StatusEvent.OPERATION_START:
        for (int i = 0; i < listeners.length; i++)
          {
            listeners[i].statusOperationStarted(event);
          }
        break;
      case StatusEvent.OPERATION_UPDATE:
        for (int i = 0; i < listeners.length; i++)
          {
            listeners[i].statusProgressUpdate(event);
          }
        break;
      case StatusEvent.OPERATION_END:
        for (int i = 0; i < listeners.length; i++)
          {
            listeners[i].statusOperationEnded(event);
          }
        break;
    }
  }

}

/*
 * MboxFolder.java
 * Copyright(C) 1999 Chris Burdess <address@hidden>
 *
 * This file is part of GNU JavaMail, a library.
 *
 * GNU JavaMail is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 *(at your option) any later version.
 *
 * GNU JavaMail is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * As a special exception, if you link this library with other files to
 * produce an executable, this library does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * This exception does not however invalidate any other reasons why the
 * executable file might be covered by the GNU General Public License.
 *
 * Contributor(s): Daniel Thor Kristjan <address@hidden>
 *                   close and expunge clarification.
 *                 Sverre Huseby <address@hidden> gzipped mailboxes
 */

package gnu.mail.providers.mbox;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.event.ConnectionEvent;
import javax.mail.event.FolderEvent;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import gnu.inet.util.LineInputStream;
import gnu.mail.treeutil.StatusEvent;

/**
 * The folder class implementing a UNIX mbox-format mailbox.
 *
 * @author <a href='mailto:address@hidden'>Chris Burdess</a>
 */
public class MboxFolder
  extends Folder
{

  static final DateFormat df = new SimpleDateFormat("EEE MMM d H:m:s yyyy");
  static final String GNU_MESSAGE_ID = "X-GNU-Message-Id";
  static final String FROM = "From ";

  File file;
  String name;
  MboxMessage[] messages;
  boolean open;
  boolean readOnly;
  int type;
  boolean inbox;

  Flags permanentFlags = null;

  /**
   * Constructor.
   */
  protected MboxFolder(MboxStore store, String name, boolean inbox)
  {
    super(store);
    this.name = name;
    if (0 < name.length() && name.charAt(0) == '/') {
      file = new File(canonicalNameToLocal(name));
    } else {
      file = new File(store.getMailRootDir(), canonicalNameToLocal(name));
    }
    if (file.exists() && file.isDirectory())
      {
        type = HOLDS_FOLDERS;
      }
    else
      {
        type = HOLDS_MESSAGES;
      }
    this.inbox = inbox;
    open = false;
    readOnly = true;
    messages = new MboxMessage[0];
  }

  /**
   * Constructor.
   */
  protected MboxFolder(MboxStore store, String name)
  {
    this(store, name, false);
  }

  /**
   * Returns the name of this folder.
   */
  public String getName()
  {
    if (inbox)
      {
        return "INBOX";
      }
    return file.getName();
  }

  /**
   * Returns the full name of this folder.
   */
  public String getFullName()
  {
    if (inbox)
      {
        return "INBOX";
      }
    return name;
  }

  /**
   * Return a URLName representing this folder.
   */
  public URLName getURLName()
    throws MessagingException
  {
    URLName url = super.getURLName();
    return new URLName(url.getProtocol(),
                       null, -1, url.getFile(),
                       null, null);
  }

  /**
   * Returns the type of this folder.
   * @exception MessagingException if a messaging error occurred
   */
  public int getType()
    throws MessagingException
  {
    return type;
  }

  /**
   * Indicates whether this folder exists.
   * @exception MessagingException if a messaging error occurred
   */
  public boolean exists()
    throws MessagingException
  {
    return file.exists();
  }

  /**
   * Indicates whether this folder contains new messages.
   * @exception MessagingException if a messaging error occurred
   */
  public boolean hasNewMessages()
    throws MessagingException
  {
    return getNewMessageCount() > 0;
  }

  /**
   * Opens this folder.
   * If the folder is opened for writing, a lock must be acquired on the
   * mbox. If this fails a MessagingException is thrown.
   * @exception MessagingException if a messaging error occurred
   */
  public void open(int mode)
    throws MessagingException
  {
    String filename = file.getAbsolutePath();
    if (mode == READ_WRITE)
      {
        if (!file.canWrite())
          {
            throw new MessagingException("Folder is read-only");
          }
        if (!acquireLock())
          {
            throw new MessagingException("Unable to acquire lock: " + filename);
          }
        readOnly = false;
      }

    if (!file.canRead())
      {
        throw new MessagingException("Can't read folder: " + file.getName());
      }

    LineInputStream in = null;
    try
      {
        // Read messages
        MboxStore mstore = (MboxStore) this.store;
        mstore.log("reading " + filename);

        List acc = new ArrayList(256);
        in = new LineInputStream(getInputStream());
        int count = 1;
        String line, fromLine = null;
        ByteArrayOutputStream buf = null;

        // notify listeners
        StatusEvent event;
        event = new StatusEvent(mstore,
                                StatusEvent.OPERATION_START,
                                "open");
        mstore.processStatusEvent(event);

        for (line = in.readLine(); line != null; line = in.readLine())
          {
            if (line.indexOf(FROM) == 0)
              {
                if (buf != null)
                  {
                    byte[] bytes = buf.toByteArray();
                    ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
                    MboxMessage m =
                      new MboxMessage(this, fromLine, bin, count++);
                    acc.add(m);

                    event = new StatusEvent(mstore,
                                            StatusEvent.OPERATION_UPDATE,
                                            "open",
                                            1,
                                            StatusEvent.UNKNOWN,
                                            count - 1);
                    mstore.processStatusEvent(event);
                  }
                fromLine = line;
                buf = new ByteArrayOutputStream();
              }
            else if (buf != null)
              {
                byte[] bytes = decodeFrom(line).getBytes();
                buf.write(bytes, 0, bytes.length);
                buf.write(10); // LF
              }
          }
        if (buf != null)
          {
            byte[] bytes = buf.toByteArray();
            ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
            MboxMessage m = new MboxMessage(this, fromLine, bin, count++);
            acc.add(m);

            event = new StatusEvent(mstore,
                                    StatusEvent.OPERATION_UPDATE,
                                    "open",
                                    1,
                                    StatusEvent.UNKNOWN,
                                    count - 1);
            mstore.processStatusEvent(event);
          }
        messages = new MboxMessage[acc.size()];
        acc.toArray(messages);
        buf = null;
        acc = null;

        event = new StatusEvent(mstore,
                                StatusEvent.OPERATION_END,
                                "open");
        mstore.processStatusEvent(event);

        // OK
        open = true;
        notifyConnectionListeners(ConnectionEvent.OPENED);
      }
    catch (IOException e)
      {
        throw new MessagingException("Unable to open folder: " + filename, e);
      }
    finally
      {
        // release any file descriptors
        try
          {
            if (in != null)
              {
                in.close();
              }
          }
        catch (IOException e)
          {
            // we tried
          }
      }
  }

  /**
   * Returns the specified line with any From_ line encoding removed.
   */
  public static String decodeFrom(String line)
  {
    if (line != null)
      {
        int len = line.length();
        for (int i = 0; i < (len - 5); i++)
          {
            char c = line.charAt(i);
            if (i > 0 &&
                (c == 'F' &&
                 line.charAt(i+1) == 'r' &&
                 line.charAt(i+2) == 'o' &&
                 line.charAt(i+3) == 'm' &&
                 line.charAt(i+4) == ' '))
              {
                return line.substring(1);
              }
            if (c != '>')
              {
                break;
              }
          }
      }
    return line;
  }

  /**
   * Closes this folder.
   * @param expunge if the folder is to be expunged before it is closed
   * @exception MessagingException if a messaging error occurred
   */
  public void close(boolean expunge)
    throws MessagingException
  {
    if (open)
      {
        if (expunge)
          {
            expunge();
          }

        if (!readOnly)
          {
            // Save messages
            MboxStore mstore = (MboxStore) this.store;
            StatusEvent event;
            mstore.log("saving " + file.getAbsolutePath());
            synchronized (this)
              {
                OutputStream os = null;
                try
                  {
                    os = getOutputStream();
                    BufferedOutputStream bos = new BufferedOutputStream(os);
                    MboxOutputStream mos = new MboxOutputStream(bos);

                    event = new StatusEvent(mstore,
                                            StatusEvent.OPERATION_START,
                                            "close");
                    mstore.processStatusEvent(event);
                    for (int i = 0; i < messages.length; i++)
                      {
                        String fromLine = fromLine(messages[i]);
                        bos.write(fromLine.getBytes());
                        bos.write('\n');
                        bos.flush();
                        messages[i].writeTo(mos);
                        mos.flush();

                        event = new StatusEvent(mstore,
                                                StatusEvent.OPERATION_UPDATE,
                                                "close",
                                                1,
                                                messages.length,
                                                i + 1);
                        mstore.processStatusEvent(event);
                      }

                    event = new StatusEvent(mstore,
                                            StatusEvent.OPERATION_END,
                                            "close");
                    mstore.processStatusEvent(event);
                  }
                catch (IOException e)
                  {
                    throw new MessagingException("I/O error writing mailbox",
                                                 e);
                  }
                finally
                  {
                    // close any file descriptors
                    try
                      {
                        if (os != null)
                          {
                            os.close();
                          }
                      }
                    catch (IOException e)
                      {
                        // we tried
                      }
                  }
              }
            if (!releaseLock())
              {
                mstore.log("unable to clear up lock file!");
              }
          }

        open = false;
        messages = new MboxMessage[0]; // release memory
        notifyConnectionListeners(ConnectionEvent.CLOSED);
      }
  }

  /**
   * Returns the From_ line for the specified mbox message.
   * If this does not already exist(the message was appended to the folder
   * since it was last opened), we will attempt to generate a suitable From_
   * line for it.
   */
  protected String fromLine(MboxMessage message)
    throws MessagingException
  {
    String fromLine = message.fromLine;
    if (fromLine == null)
      {
        StringBuffer buf = new StringBuffer("From ");

        String from = "-";
        try
          {
            Address[] f = message.getFrom();
            if (f != null && f.length > 0)
              {
                if (f[0] instanceof InternetAddress)
                  {
                    from = ((InternetAddress) f[0]).getAddress();
                  }
                else
                  {
                    from = f[0].toString();
                  }
              }
          }
        catch (AddressException e)
          {
            // these things happen...
          }
        buf.append(from);
        buf.append(' ');

        Date date = message.getSentDate();
        if (date==null)
          {
            date = message.getReceivedDate();
          }
        if (date==null)
          {
            date = new Date();
          }
        buf.append(df.format(date));

        fromLine = buf.toString();
      }
    return fromLine;
  }

  /**
   * Expunges this folder.
   * This deletes all the messages marked as deleted.
   * @exception MessagingException if a messaging error occurred
   */
  public Message[] expunge()
    throws MessagingException
  {
    Message[] expunged;
    synchronized (this)
      {
        List elist = new ArrayList();
        if (open)
          {
            List mlist = new ArrayList();
            for (int i=0; i<messages.length; i++)
              {
                Flags flags = messages[i].getFlags();
                if (flags.contains(Flags.Flag.DELETED))
                  {
                    elist.add(messages[i]);
                    if (messages[i] instanceof MboxMessage)
                      {
                        ((MboxMessage)messages[i]).setExpunged(true);
                      }
                  }
                else
                  {
                    mlist.add(messages[i]);
                  }
              }
            messages = new MboxMessage[mlist.size()];
            mlist.toArray(messages);
          }
        expunged = new Message[elist.size()];
        elist.toArray(expunged);
      }
    if (expunged.length > 0)
      {
        notifyMessageRemovedListeners(true, expunged);
      }
    return expunged;
  }

  /**
   * Indicates whether this folder is open.
   */
  public boolean isOpen()
  {
    return open;
  }

  /**
   * Returns the permanent flags for this folder.
   */
  public Flags getPermanentFlags()
  {
    if (permanentFlags == null)
      {
        Flags flags = new Flags();
        flags.add(Flags.Flag.DELETED);
        flags.add(Flags.Flag.SEEN);
        flags.add(Flags.Flag.RECENT);
        permanentFlags = flags;
      }
    return permanentFlags;
  }

  /**
   * Returns the number of messages in this folder.
   * @exception MessagingException if a messaging error occurred
   */
  public int getMessageCount()
    throws MessagingException
  {
    return messages.length;
  }

  /**
   * Returns the specified message number from this folder.
   * @exception MessagingException if a messaging error occurred
   */
  public Message getMessage(int msgnum)
    throws MessagingException
  {
    int index = msgnum-1;
    if (index < 0 || index >= messages.length)
      {
        throw new MessagingException("No such message: "+msgnum);
      }
    return messages[index];
  }

  /**
   * Returns the messages in this folder.
   * @exception MessagingException if a messaging error occurred
   */
  public synchronized Message[] getMessages()
    throws MessagingException
  {
    // Return a copy of the message array
    Message[] m = new Message[messages.length];
    System.arraycopy(messages, 0, m, 0, messages.length);
    return m;
  }

  /**
   * Appends messages to this folder.
   * Only MimeMessages within the array will be appended, as we don't know
   * how to retrieve internet content for other kinds.
   * @param m an array of messages to be appended
   */
  public synchronized void appendMessages(Message[] m)
    throws MessagingException
  {
    MboxMessage[] n;
    synchronized (this)
      {
        List appended = new ArrayList(m.length);
        int count = messages.length;
        for (int i = 0; i < m.length; i++)
          {
            if (m[i] instanceof MimeMessage)
              {
                MimeMessage mimem = (MimeMessage) m[i];
                MboxMessage mboxm = new MboxMessage(this, mimem, count++);
                if (mimem instanceof MboxMessage)
                  {
                    mboxm.fromLine = ((MboxMessage) mimem).fromLine;
                  }
                appended.add(mboxm);
              }
          }
        n = new MboxMessage[appended.size()];
        if (n.length>0)
          {
            appended.toArray(n);
            // copy into the messages array
            List acc = new ArrayList(messages.length+n.length);
            acc.addAll(Arrays.asList(messages));
            acc.addAll(Arrays.asList(n));
            messages = new MboxMessage[acc.size()];
            acc.toArray(messages);
            acc = null;
          }
      }
    // propagate event
    if (n.length > 0)
      {
        notifyMessageAddedListeners(n);
      }
  }

  /**
   * Returns the parent folder.
   */
  public Folder getParent()
    throws MessagingException
  {
    StringBuffer newName = new StringBuffer(name);
    while (newName.charAt(newName.length()-1) == '/') {
      newName.setLength(newName.length()-1);
    }
    if (newName.equals("")) {
      return store.getDefaultFolder();
    }
    while (newName.charAt(newName.length()-1) != '/') {
      newName.setLength(newName.length()-1);
    }
    return store.getFolder(newName.toString());
  }

static String canonicalNameToLocal(String name) {
  // convert into a valid filename for this platform
  StringBuffer buf = new StringBuffer();
  if ('/' != File.separatorChar) {
    String rtn = name.replace('/', File.separatorChar);
    // This allows you to use "//c:/foo/bar" as a hack for "c:/foo/bar".
    // This is because the MboxStore specification specifies that only
    // names starting with the separator ( / ) are treated absolute.
    if (2 <= name.length() && name.charAt(0) == '/' && name.charAt(1) == '/') {
      rtn = rtn.substring(2);
    }
    return rtn;
  }
  else {
   return name;
  }
}
  /**
   * Returns the subfolders of this folder.
   */
  public Folder[] list()
    throws MessagingException
  {
    if (type != HOLDS_FOLDERS)
      {
        throw new MessagingException("This folder can't contain subfolders");
      }
    try
      {
        String[] files = file.list();
        Folder[] folders = new Folder[files.length];
        for (int i = 0; i < files.length; i++)
          {
            folders[i] = store.getFolder(makeName(name, files[i]));
          }
        return folders;
      }
    catch (SecurityException e)
      {
        throw new MessagingException("Access denied", e);
      }
  }

  /**
   * Returns the subfolders of this folder matching the specified pattern.
   */
  public Folder[] list(String pattern)
    throws MessagingException
  {
    if (type != HOLDS_FOLDERS)
      {
        throw new MessagingException("This folder can't contain subfolders");
      }
    try
      {
        String[] files = file.list(new MboxFilenameFilter(pattern));
        Folder[] folders = new Folder[files.length];
        for (int i = 0; i < files.length; i++)
          {
            folders[i] = store.getFolder(makeName(name, files[i]));
          }
        return folders;
      }
    catch (SecurityException e)
      {
        throw new MessagingException("Access denied", e);
      }
  }

  /**
   * Returns the separator character.
   */
  public char getSeparator()
    throws MessagingException
  {
    return File.separatorChar;
  }

  /**
   * Creates this folder in the store.
   */
  public boolean create(int type)
    throws MessagingException
  {
    if (file.exists())
      {
        throw new MessagingException("Folder already exists");
      }
    switch (type)
      {
      case HOLDS_FOLDERS:
        try
          {
            file.mkdirs();
            this.type = type;
            notifyFolderListeners(FolderEvent.CREATED);
            return true;
          }
        catch (SecurityException e)
          {
            throw new MessagingException("Access denied", e);
          }
      case HOLDS_MESSAGES:
        try
          {
            synchronized (this)
              {
                createNewFile(file);
              }
            this.type = type;
            notifyFolderListeners(FolderEvent.CREATED);
            return true;
          }
        catch (IOException e)
          {
            throw new MessagingException("I/O error writing mailbox", e);
          }
        catch (SecurityException e)
          {
            throw new MessagingException("Access denied", e);
          }
      }
    return false;
  }

  /**
   * Deletes this folder.
   */
  public boolean delete(boolean recurse)
    throws MessagingException
  {
    if (recurse)
      {
        try
          {
            if (type == HOLDS_FOLDERS)
              {
                Folder[] folders = list();
                for (int i = 0; i < folders.length; i++)
                  {
                    if (!folders[i].delete(recurse))
                      {
                        return false;
                      }
                  }
              }
            if (!readOnly)
              {
                releaseLock();
              }
            if (!file.delete())
              {
                return false;
              }
            notifyFolderListeners(FolderEvent.DELETED);
            return true;
          }
        catch (SecurityException e)
          {
            throw new MessagingException("Access denied", e);
          }
      }
    else
      {
        try
          {
            if (type == HOLDS_FOLDERS)
              {
                Folder[] folders = list();
                if (folders.length > 0)
                  {
                    return false;
                  }
              }
            if (!readOnly)
              {
                releaseLock();
              }
            if (!file.delete())
              {
                return false;
              }
            notifyFolderListeners(FolderEvent.DELETED);
            return true;
          }
        catch (SecurityException e)
          {
            throw new MessagingException("Access denied", e);
          }
      }
  }

  /**
   * Renames this folder.
   */
  public boolean renameTo(Folder folder)
    throws MessagingException
  {
    try
      {
        if (folder.getFullName() != null)
        {
        File newfile = new File(((MboxStore)getStore()).getMailRootDir(), 
canonicalNameToLocal(folder.getFullName()));
            if (!file.renameTo(newfile))
              {
                return false;
              }
            notifyFolderRenamedListeners(folder);
            return true;
          }
        else
          {
            throw new MessagingException("Illegal filename: null");
          }
      }
    catch (SecurityException e)
      {
        throw new MessagingException("Access denied", e);
      }
  }

  /**
   * Returns the subfolder of this folder with the specified name.
   */
  public Folder getFolder(String fname)
    throws MessagingException
  {
/*    String INBOX = "/INBOX";
    if (INBOX.equalsIgnoreCase(fname))
      {
        try
          {
            return store.getFolder(INBOX);
          }
        catch (MessagingException e)
          {
            // fall back to standard behaviour
          }
      } */

if (0 < fname.length() && fname.charAt(0) == '/') {
  return store.getFolder(fname);
} else {
  return store.getFolder(makeName(name, fname));
}
  }

  static String makeName(String parent, String child) {
      StringBuffer rtn = new StringBuffer();
          if (parent.length() == 0) {
                  rtn.append(child);
          } else if (parent.charAt(parent.length() - 1) == '/') {
                  rtn.append(parent);
                  rtn.append(child);
          } else {
                  rtn.append(parent);
                  rtn.append('/');
                  rtn.append(child);
      }
      return rtn.toString();
  }
  /**
   * Checks if the current file is or is supposed to be
   * compressed. Uses the filename to figure it out.
   */
  private boolean isGzip()
  {
    return file.getName().toLowerCase().endsWith(".gz");
  }

  /**
   * Creates an input stream that possibly will decompress the
   * file contents.
   */
  private InputStream getInputStream()
    throws IOException
  {
    InputStream in = new FileInputStream(file);
    if (isGzip())
      {
        in = new GZIPInputStream(in);
      }
    return in;
  }

  /**
   * Creates an output stream that possibly will compress
   * whatever is sent to it, based on the current filename.
   */
  private OutputStream getOutputStream()
    throws IOException
  {
    OutputStream out = new FileOutputStream(file);
    if (isGzip())
      {
        out = new GZIPOutputStream(out);
      }
    return out;
  }

  /**
   * Locks this mailbox.
   * This uses a dotlock-like mechanism - see createNewFile().
   * If the directory containing the mbox
   * folder is not writable, we will not be able to open the mbox for
   * writing either.
   */
  public synchronized boolean acquireLock()
  {
    try
      {
        String filename = file.getPath();
        String lockFilename = filename + ".lock";
        File lock = new File(lockFilename);
        MboxStore mstore = (MboxStore) this.store;
        mstore.log("creating " + lock.getPath());
        if (lock.exists())
          {
            return false;
          }
        //if (!lock.canWrite())
        //  return false;
        createNewFile(lock);
        return true;
      }
    catch (IOException e)
      {
        MboxStore mstore = (MboxStore)this.store;
        mstore.log("I/O exception acquiring lock on " + file.getPath());
      }
    catch (SecurityException e)
      {
        MboxStore mstore = (MboxStore)this.store;
        mstore.log("Security exception acquiring lock on " + file.getPath());
      }
    return false;
  }

  /**
   * This method creates a new file.
   * Because Java cannot properly dotlock a file by creating a temporary
   * file and hardlinking it(some platforms do not support hard links) we
   * must use this method to create a zero-length inode.
   * This is a replacement for File.createNewFile(), which only exists in
   * the JDK since 1.2.
   * The idea is simply to touch the specified file.
   */
  private void createNewFile(File file)
    throws IOException
  {
    // there may be another, more efficient way to do this.
    // certainly just setLastModified() does not work.
    BufferedOutputStream out =
      new BufferedOutputStream(new FileOutputStream(file));
    out.flush();
    out.close();
  }

  /**
   * Unlocks this mailbox.
   * This deletes any associated lockfile if it exists. It returns false if
   * an existing lockfile could not be deleted.
   */
  public synchronized boolean releaseLock()
  {
    try
      {
        String filename = file.getPath();
        String lockFilename = filename + ".lock";
        File lock = new File(lockFilename);
        MboxStore mstore = (MboxStore) this.store;
        mstore.log("removing "+lock.getPath());
        if (lock.exists())
          {
            if (!lock.delete())
              {
                return false;
              }
          }
        return true;
      }
    catch (SecurityException e)
      {
        MboxStore mstore = (MboxStore) this.store;
        mstore.log("Security exception releasing lock on " + file.getPath());
      }
    return false;
  }

  class MboxFilenameFilter
    implements FilenameFilter
  {

    String pattern;
    int asteriskIndex, percentIndex;

    MboxFilenameFilter(String pattern)
    {
      this.pattern = pattern;
      asteriskIndex = pattern.indexOf('*');
      percentIndex = pattern.indexOf('%');
    }

    public boolean accept(File directory, String name)
    {
      if (asteriskIndex>-1)
        {
          String start = pattern.substring(0, asteriskIndex);
          String end = pattern.substring(asteriskIndex+1, pattern.length());
          return (name.startsWith(start) &&
                  name.endsWith(end));
        }
      else if (percentIndex>-1)
        {
          String start = pattern.substring(0, percentIndex);
          String end = pattern.substring(percentIndex+1, pattern.length());
          return (directory.equals(file) &&
                  name.startsWith(start) &&
                  name.endsWith(end));
        }
      return name.equals(pattern);
    }
  }

}

reply via email to

[Prev in Thread] Current Thread [Next in Thread]