root/trunk/LogicMail/src/org/logicprobe/LogicMail/model/MailFileManager.java

Revision 704, 19.3 KB (checked in by octorian, 27 hours ago)

Cleanup of inappropriate getAccountConfig() calls

Line 
1/*-
2 * Copyright (c) 2009, Derek Konigsberg
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the project nor the names of its
15 *    contributors may be used to endorse or promote products derived
16 *    from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31package org.logicprobe.LogicMail.model;
32
33import java.io.DataInputStream;
34import java.io.DataOutputStream;
35import java.io.IOException;
36import java.util.Enumeration;
37import java.util.Hashtable;
38import java.util.Vector;
39
40import javax.microedition.io.Connector;
41import javax.microedition.io.file.FileConnection;
42
43import net.rim.device.api.system.EventLogger;
44import net.rim.device.api.util.InvertedOrderComparator;
45import net.rim.device.api.util.SimpleSortingVector;
46
47import org.logicprobe.LogicMail.AppInfo;
48import org.logicprobe.LogicMail.conf.GlobalConfig;
49import org.logicprobe.LogicMail.conf.MailSettings;
50import org.logicprobe.LogicMail.conf.MailSettingsEvent;
51import org.logicprobe.LogicMail.conf.MailSettingsListener;
52import org.logicprobe.LogicMail.mail.MessageToken;
53import org.logicprobe.LogicMail.message.MimeMessageContent;
54import org.logicprobe.LogicMail.util.StringParser;
55
56/**
57 * Front-end for reading and writing messages from local file storage.
58 */
59public class MailFileManager {
60        private static MailFileManager instance;
61        private MailSettings mailSettings;
62        private String cacheUrl;
63       
64        private static String CACHE_PREFIX = "cache/";
65        private static String MSG_SUFFIX = ".msg";
66        private static String MSG_FILTER = "*.msg";
67        private static String LOCAL_ACCOUNT_UID = "local";
68    private static String LOCAL_OUTBOX_UID = "Outbox";
69       
70        /**
71         * Instantiates a new mail file manager.
72         */
73    private MailFileManager() {
74        mailSettings = MailSettings.getInstance();
75
76        // Register a listener for configuration changes
77        mailSettings.addMailSettingsListener(new MailSettingsListener() {
78            public void mailSettingsSaved(MailSettingsEvent e) {
79                if((e.getGlobalChange() & GlobalConfig.CHANGE_TYPE_DATA) != 0) {
80                    refreshConfiguration();
81                }
82            }
83        });
84
85        refreshConfiguration();
86    }
87       
88        /**
89         * Gets the single instance of MailFileManager.
90         *
91         * @return single instance of MailFileManager
92         */
93        public static synchronized MailFileManager getInstance() {
94                if(instance == null) {
95                        instance = new MailFileManager();
96                }
97                return instance;
98        }
99       
100        /**
101         * Refreshes the configuration based on any system configuration changes.
102         */
103        synchronized void refreshConfiguration() {
104                String localDataLocation = mailSettings.getGlobalConfig().getLocalDataLocation();
105                String newCacheUrl = localDataLocation + CACHE_PREFIX;
106                if(!newCacheUrl.equals(cacheUrl)) {
107                        FileConnection fileConnection;
108                        try {
109                                fileConnection = (FileConnection)Connector.open(localDataLocation);
110                                if(!fileConnection.exists()) {
111                                        fileConnection.mkdir();
112                                }
113                                fileConnection.close();
114                               
115                                fileConnection = (FileConnection)Connector.open(newCacheUrl);
116                                if(!fileConnection.exists()) {
117                                        fileConnection.mkdir();
118                                }
119                                fileConnection.close();
120                                cacheUrl = newCacheUrl;
121                        } catch (IOException e) {
122                                EventLogger.logEvent(AppInfo.GUID,
123                                ("Unable to open cache: " + newCacheUrl
124                                        + "\r\n" + e.getMessage()).getBytes(),
125                                EventLogger.ERROR);
126                                cacheUrl = null;
127                        }
128                }
129        }
130       
131        /**
132         * Write a message node to local storage.
133         * If data already exists matching the file path convention for the provided
134         * message, it will be overwritten.
135         *
136         * @param messageNode the message node
137         *
138         * @throws IOException Signals that an I/O exception has occurred.
139         */
140        public synchronized void writeMessage(MessageNode messageNode) throws IOException {
141                if(cacheUrl == null) { return; }
142               
143                // Create a file connection for the message
144                FileConnection fileConnection = getMailboxFileConnection(messageNode.getParent());
145                String url = fileConnection.getURL();
146                fileConnection.close();
147                fileConnection = (FileConnection)Connector.open(
148                                url
149                                + messageNode.getMessageToken().getMessageUid()
150                                + MSG_SUFFIX);
151                if(fileConnection.exists()) {
152                        fileConnection.truncate(0);
153                }
154                else {
155                        fileConnection.create();
156                }
157               
158                // Create the writer
159                DataOutputStream output = fileConnection.openDataOutputStream();
160                MessageNodeWriter writer = new MessageNodeWriter(output);
161               
162                // Write the node
163                writer.write(messageNode);
164               
165                // Close and cleanup
166                output.close();
167                fileConnection.close();
168        }
169       
170        /**
171         * Read message tokens for all the messages contained within the local
172         * storage location backing the provided mailbox node.
173         *
174         * @param mailboxNode the mailbox node
175         *
176         * @return the message tokens
177         *
178         * @throws IOException Signals that an I/O exception has occurred.
179         */
180        public synchronized MessageToken[] readMessageTokens(MailboxNode mailboxNode) throws IOException {
181                if(cacheUrl == null) { return new MessageToken[0]; }
182               
183                //TODO: Implement an indexing mechanism to avoid listing the whole directory
184                //TODO: Provide support for partial listing requests
185               
186                Vector messageTokenList = new Vector();
187               
188        String[] fileUrls = getMessageFiles(mailboxNode);
189        for(int i=0; i<fileUrls.length; i++) {
190            MessageToken messageToken = readMessageToken(fileUrls[i]);
191            if(messageToken != null) {
192                messageTokenList.addElement(messageToken);
193            }
194        }
195       
196                MessageToken[] result = new MessageToken[messageTokenList.size()];
197                messageTokenList.copyInto(result);
198                return result;
199        }
200       
201        public synchronized MessageNode[] readMessageNodes(MailboxNode mailboxNode) throws IOException {
202            return readMessageNodes(mailboxNode, false);
203        }
204       
205        public synchronized MessageNode[] readMessageNodes(MailboxNode mailboxNode, boolean loadContent) throws IOException {
206                if(cacheUrl == null) { return new MessageNode[0]; }
207
208                Vector messageNodeList = new Vector();
209               
210        String[] fileUrls = getMessageFiles(mailboxNode);
211        for(int i=0; i<fileUrls.length; i++) {
212            MessageNode messageNode = readMessageNode(fileUrls[i]);
213            if(messageNode != null) {
214                if(loadContent) {
215                    MimeMessageContent[] content = readMessageContent(mailboxNode, messageNode.getMessageToken());
216                    messageNode.putMessageContent(content);
217                }
218                messageNodeList.addElement(messageNode);
219            }
220        }
221               
222                MessageNode[] result = new MessageNode[messageNodeList.size()];
223                messageNodeList.copyInto(result);
224                return result;
225        }
226       
227        public synchronized void readMessageNodes(MailboxNode mailboxNode, MessageNodeCallback callback) throws IOException {
228            readMessageNodes(mailboxNode, false, callback);
229        }
230       
231    public synchronized void readMessageNodes(MailboxNode mailboxNode, boolean loadContent, MessageNodeCallback callback) throws IOException {
232        if(cacheUrl == null) { callback.messageNodeUpdated(null); }
233       
234        String[] fileUrls = getMessageFiles(mailboxNode);
235        Vector fileUrlsToLoad = new Vector(fileUrls.length);
236        for(int i=0; i<fileUrls.length; i++) {
237            int p = fileUrls[i].lastIndexOf('/');
238            int q = fileUrls[i].lastIndexOf('.');
239            if(p != -1 && q != -1 && p < q) {
240                String messageUid = fileUrls[i].substring(p + 1, q);
241                if(callback.messageNodeAvailable(messageUid)) {
242                    fileUrlsToLoad.addElement(fileUrls[i]);
243                }
244            }
245        }
246       
247        int size = fileUrlsToLoad.size();
248        for(int i=0; i<size; i++) {
249            MessageNode messageNode = readMessageNode((String)fileUrlsToLoad.elementAt(i));
250            if(messageNode != null) {
251                if(loadContent) {
252                    MimeMessageContent[] content = readMessageContent(mailboxNode, messageNode.getMessageToken());
253                    messageNode.putMessageContent(content);
254                }
255                callback.messageNodeUpdated(messageNode);
256            }
257        }
258       
259        callback.messageNodeUpdated(null);
260    }
261       
262    private String[] getMessageFiles(MailboxNode mailboxNode) throws IOException {
263        SimpleSortingVector fileVector = new SimpleSortingVector();
264        if(mailSettings.getGlobalConfig().getDispOrder()) {
265            fileVector.setSortComparator(new MailFileComparator());
266        }
267        else {
268            fileVector.setSortComparator(new InvertedOrderComparator(new MailFileComparator()));
269        }
270        fileVector.setSort(true);
271       
272        FileConnection fileConnection = getMailboxFileConnection(mailboxNode);
273        String mailboxUrl = fileConnection.getURL();
274        Enumeration e = fileConnection.list(MSG_FILTER, false);
275        while(e.hasMoreElements()) {
276            fileVector.addElement(e.nextElement());
277        }
278        fileConnection.close();
279
280        int size = fileVector.size();
281        String[] result = new String[size];
282        for(int i=0; i<size; i++) {
283            result[i] = mailboxUrl + fileVector.elementAt(i);
284        }
285        return result;
286    }
287   
288        public synchronized MessageNode readMessageNode(MailboxNode mailboxNode, MessageToken messageToken, boolean loadContent) throws IOException {
289                if(cacheUrl == null) { return null; }
290
291                FileConnection fileConnection = getMailboxFileConnection(mailboxNode);
292                if(!fileConnection.exists()) { return null; }
293               
294                String mailboxUrl = fileConnection.getURL();
295                fileConnection.close();
296                String fileUrl =
297                                mailboxUrl
298                                + messageToken.getMessageUid()
299                                + MSG_SUFFIX;
300                MessageNode messageNode = readMessageNode(fileUrl);
301                if(loadContent) {
302                        MimeMessageContent[] content = readMessageContent(mailboxNode, messageToken);
303                        messageNode.putMessageContent(content);
304                }
305                return messageNode;
306        }
307       
308        public synchronized MimeMessageContent[] readMessageContent(MailboxNode mailboxNode, MessageToken messageToken) throws IOException {
309                if(cacheUrl == null) { return null; }
310               
311                FileConnection fileConnection = getMailboxFileConnection(mailboxNode);
312                if(!fileConnection.exists()) { return null; }
313               
314                String mailboxUrl = fileConnection.getURL();
315                fileConnection.close();
316                String fileUrl =
317                                mailboxUrl
318                                + messageToken.getMessageUid()
319                                + MSG_SUFFIX;
320                MimeMessageContent[] messageContent = readMessageContent(fileUrl, messageToken);
321               
322                return messageContent;
323        }
324       
325        private FileConnection getMailboxFileConnection(MailboxNode mailboxNode) throws IOException {
326                // Build the account and mailbox UID strings
327            String accountUid;
328            String mailboxUid;
329            if(mailboxNode instanceof OutboxMailboxNode) {
330                accountUid = LOCAL_ACCOUNT_UID;
331                mailboxUid = LOCAL_OUTBOX_UID;
332            }
333            else {
334                accountUid = StringParser.toHexString(
335                                ((NetworkAccountNode)mailboxNode.getParentAccount()).getUniqueId()).toLowerCase();
336                mailboxUid = StringParser.toHexString(
337                                mailboxNode.getUniqueId()).toLowerCase();
338            }
339           
340                StringBuffer buf = new StringBuffer(cacheUrl);
341               
342                // Open the account directory, creating if necessary
343                buf.append(accountUid); buf.append('/');
344                FileConnection fileConnection = (FileConnection)Connector.open(buf.toString());
345                if(!fileConnection.exists()) {
346                        fileConnection.mkdir();
347                }
348                fileConnection.close();
349               
350                // Traverse to the mailbox directory, creating if necessary
351                buf.append(mailboxUid); buf.append('/');
352                fileConnection = (FileConnection)Connector.open(buf.toString());
353                if(!fileConnection.exists()) {
354                        fileConnection.mkdir();
355                }
356               
357                return fileConnection;
358        }
359       
360        private MessageToken readMessageToken(String fileUrl) {
361                MessageToken result;
362                FileConnection fileConnection = null;
363                try {
364                        fileConnection = (FileConnection)Connector.open(fileUrl);
365                        DataInputStream input = fileConnection.openDataInputStream();
366                        MessageNodeReader reader = new MessageNodeReader(input);
367                        result = reader.read();
368                        input.close();
369                        fileConnection.close();
370                } catch (Exception e) {
371                        EventLogger.logEvent(AppInfo.GUID,
372                        ("Unable to read token: " + fileUrl
373                                + "\r\n" + e.getMessage()).getBytes(),
374                        EventLogger.ERROR);
375                        result = null;
376                } finally {
377                        if(fileConnection != null) {
378                                try { fileConnection.close(); } catch (Exception e) { }
379                        }
380                }
381                return result;
382        }
383       
384        private MessageNode readMessageNode(String fileUrl) {
385                MessageNode result;
386                FileConnection fileConnection = null;
387                try {
388                        fileConnection = (FileConnection)Connector.open(fileUrl);
389                        DataInputStream input = fileConnection.openDataInputStream();
390                        MessageNodeReader reader = new MessageNodeReader(input);
391                        result = reader.readMessageNode();
392                        result.setCached(true);
393                        input.close();
394                        fileConnection.close();
395                } catch (Exception e) {
396                        EventLogger.logEvent(AppInfo.GUID,
397                        ("Unable to read token: " + fileUrl
398                                + "\r\n" + e.getMessage()).getBytes(),
399                        EventLogger.ERROR);
400                        result = null;
401                } finally {
402                        if(fileConnection != null) {
403                                try { fileConnection.close(); } catch (Exception e) { }
404                        }
405                }
406                return result;
407        }
408       
409        private MimeMessageContent[] readMessageContent(String fileUrl, MessageToken messageToken) {
410                FileConnection fileConnection = null;
411                Vector content = new Vector();
412                try {
413                        fileConnection = (FileConnection)Connector.open(fileUrl);
414                        DataInputStream input = fileConnection.openDataInputStream();
415                        MessageNodeReader reader = new MessageNodeReader(input);
416                        MessageToken tempToken = reader.read();
417                        if(messageToken.equals(tempToken)) {
418                                while(reader.isContentAvailable()) {
419                                        content.addElement(reader.getNextContent());
420                                }
421                        }
422                        input.close();
423                        fileConnection.close();
424                } catch (Exception e) {
425                        EventLogger.logEvent(AppInfo.GUID,
426                        ("Unable to read content: " + fileUrl
427                                + "\r\n" + e.getMessage()).getBytes(),
428                        EventLogger.ERROR);
429                } finally {
430                        if(fileConnection != null) {
431                                try { fileConnection.close(); } catch (Exception e) { }
432                        }
433                }
434                MimeMessageContent[] result = new MimeMessageContent[content.size()];
435                content.copyInto(result);
436                return result;
437        }
438       
439        public synchronized void removeStaleMessageNodes(MailboxNode mailboxNode, String[] uidsToRetain) throws IOException {
440            if(cacheUrl == null) { return; }
441           
442            Hashtable retentionSet = new Hashtable();
443            for(int i=0; i<uidsToRetain.length; i++) {
444                retentionSet.put(uidsToRetain[i], Boolean.TRUE);
445            }
446           
447        String[] fileUrls = getMessageFiles(mailboxNode);
448        Vector fileUrlsToDelete = new Vector();
449        for(int i=0; i<fileUrls.length; i++) {
450            int p = fileUrls[i].lastIndexOf('/');
451            int q = fileUrls[i].lastIndexOf('.');
452            if(p != -1 && q != -1 && p < q) {
453                String messageUid = fileUrls[i].substring(p + 1, q);
454                if(!retentionSet.containsKey(messageUid)) {
455                    fileUrlsToDelete.addElement(fileUrls[i]);
456                }
457            }
458        }
459       
460        int size = fileUrlsToDelete.size();
461        for(int i=0; i<size; i++) {
462            try {
463                FileConnection fc = (FileConnection)Connector.open((String)fileUrlsToDelete.elementAt(i));
464                if(fc.exists() && fc.canRead()) {
465                    fc.delete();
466                }
467            } catch (IOException exp) {
468                if (EventLogger.getMinimumLevel() >= EventLogger.DEBUG_INFO) {
469                    EventLogger.logEvent(AppInfo.GUID,
470                            ("Error deleting message from cache: " + exp.toString()).getBytes(),
471                            EventLogger.DEBUG_INFO);
472                }
473            }
474        }
475        }
476       
477        public synchronized void removeMessageNodes(MailboxNode mailboxNode, MessageToken[] messageTokens) throws IOException {
478        if(cacheUrl == null) { return; }
479
480        FileConnection fileConnection = getMailboxFileConnection(mailboxNode);
481        String mailboxUrl = fileConnection.getURL();
482        fileConnection.close();
483       
484        for(int i=0; i<messageTokens.length; i++) {
485            try {
486                FileConnection mailFileConnection =
487                    (FileConnection)Connector.open(mailboxUrl + messageTokens[i].getMessageUid() + MSG_SUFFIX);
488                if(mailFileConnection.exists() && !mailFileConnection.isDirectory() && mailFileConnection.canRead()) {
489                    mailFileConnection.delete();
490                }
491                mailFileConnection.close();
492            } catch (IOException exp) {
493                if (EventLogger.getMinimumLevel() >= EventLogger.DEBUG_INFO) {
494                    EventLogger.logEvent(AppInfo.GUID,
495                            ("Error deleting message from cache: " + exp.toString()).getBytes(),
496                            EventLogger.DEBUG_INFO);
497                }
498            }
499        }
500        }
501
502    public synchronized void removeMessageNode(MailboxNode mailboxNode, MessageToken messageToken) throws IOException {
503        if(cacheUrl == null) { return; }
504
505        FileConnection fileConnection = getMailboxFileConnection(mailboxNode);
506        String mailboxUrl = fileConnection.getURL();
507        fileConnection.close();
508       
509        try {
510            FileConnection mailFileConnection =
511                (FileConnection)Connector.open(mailboxUrl + messageToken.getMessageUid() + MSG_SUFFIX);
512            if(mailFileConnection.exists() && !mailFileConnection.isDirectory() && mailFileConnection.canRead()) {
513                mailFileConnection.delete();
514            }
515            mailFileConnection.close();
516        } catch (IOException exp) {
517            if (EventLogger.getMinimumLevel() >= EventLogger.DEBUG_INFO) {
518                EventLogger.logEvent(AppInfo.GUID,
519                        ("Error deleting message from cache: " + exp.toString()).getBytes(),
520                        EventLogger.DEBUG_INFO);
521            }
522        }
523    }
524}
Note: See TracBrowser for help on using the browser.