source: trunk/LogicMail/src/org/logicprobe/LogicMail/model/MailManager.java @ 939

Revision 939, 19.5 KB checked in by octorian, 6 months ago (diff)

Added basic support for detecting device poweroff/poweron to connection handling (refs #329)

Line 
1/*-
2 * Copyright (c) 2008, 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.util.Vector;
34
35import net.rim.device.api.system.SystemListener;
36
37import org.logicprobe.LogicMail.conf.AccountConfig;
38import org.logicprobe.LogicMail.conf.GlobalConfig;
39import org.logicprobe.LogicMail.conf.MailSettings;
40import org.logicprobe.LogicMail.conf.MailSettingsEvent;
41import org.logicprobe.LogicMail.conf.MailSettingsListener;
42import org.logicprobe.LogicMail.conf.OutgoingConfig;
43import org.logicprobe.LogicMail.mail.AbstractMailSender;
44import org.logicprobe.LogicMail.mail.MailConnectionListener;
45import org.logicprobe.LogicMail.mail.MailConnectionLoginEvent;
46import org.logicprobe.LogicMail.mail.MailConnectionManager;
47import org.logicprobe.LogicMail.mail.MailConnectionStateEvent;
48import org.logicprobe.LogicMail.mail.MailConnectionStatusEvent;
49import org.logicprobe.LogicMail.mail.MailFactory;
50import org.logicprobe.LogicMail.mail.NetworkMailSender;
51import org.logicprobe.LogicMail.mail.NetworkMailStore;
52import org.logicprobe.LogicMail.util.EventListenerList;
53
54/**
55 * Singleton that provides external access to the mail data model.
56 * Ensures that clients have a fully controlled point from which
57 * to get their instances of data and utility classes.
58 */
59public class MailManager {
60        private static MailManager instance = null;
61        private volatile boolean startupComplete;
62        private final EventListenerList listenerList = new EventListenerList();
63        private final MailRootNode mailRootNode;
64        private final MailSettings mailSettings;
65        private final FolderMessageCache folderMessageCache;
66    private OutboxMailboxNode outboxMailboxNode;
67    private final SystemListener systemListener;
68   
69        /**
70         * Constructor.
71         */
72        private MailManager() {
73                mailSettings = MailSettings.getInstance();
74                mailRootNode = new MailRootNode();
75                folderMessageCache = new FolderMessageCache();
76                folderMessageCache.restore();
77
78                // Make sure the initial configuration is loaded
79                updateMailModelAccountList();
80        fireMailConfigurationChanged();
81               
82                // Register a listener for configuration changes
83                MailSettings.getInstance().addMailSettingsListener(new MailSettingsListener() {
84                        public void mailSettingsSaved(MailSettingsEvent e) {
85                            mailSettings_MailSettingsSaved(e);
86                        }
87                });
88               
89                MailConnectionManager.getInstance().addMailConnectionListener(new MailConnectionListener() {
90                        public void mailConnectionStateChanged(MailConnectionStateEvent e) {
91                                mailConnectionManager_MailConnectionStateChanged(e);
92                        }
93                        public void mailConnectionStatus(MailConnectionStatusEvent e) { }
94                        public void mailConnectionError(MailConnectionStatusEvent e) { }
95                        public void mailConnectionLogin(MailConnectionLoginEvent e) { }
96                });
97               
98                // Refresh data from local account node
99                LocalAccountNode localAccount = mailRootNode.getLocalAccount();
100                localAccount.refreshMailboxes();
101                localAccount.refreshMailboxStatus();
102
103                MailboxNode[] localMailboxes = mailRootNode.getLocalAccount().getRootMailbox().getMailboxes();
104                for(int i=0; i<localMailboxes.length; i++) {
105                // Find the outbox node, and save a reference to it for easy access
106                        if(localMailboxes[i] instanceof OutboxMailboxNode) {
107                                outboxMailboxNode = (OutboxMailboxNode)localMailboxes[i];
108                        }
109                       
110                        // Refresh messages for local mailboxes
111                        localMailboxes[i].refreshMessages(true);
112                }
113               
114                systemListener = new SystemListener() {
115            public void powerUp() {
116                handleSystemPowerUp();
117            }
118            public void powerOff() {
119                handleSystemPowerOff();
120            }
121            public void batteryGood() { }
122            public void batteryLow() { }
123            public void batteryStatusChange(int status) { }
124        };
125        }
126
127    private void refreshMailboxTypes() {
128        AccountNode[] accounts = mailRootNode.getAccounts();
129        for(int i=0; i<accounts.length; i++) {
130            if(accounts[i] instanceof NetworkAccountNode) {
131                clearMailboxTypes(accounts[i].getRootMailbox());
132            }
133        }
134           
135                int num = mailSettings.getNumAccounts();
136                for(int i=0; i<num; i++) {
137                        AccountConfig accountConfig = mailSettings.getAccountConfig(i);
138                        MailboxNode mailbox;
139                        mailbox = accountConfig.getDraftMailbox();
140                        if(mailbox != null) { mailbox.setType(MailboxNode.TYPE_DRAFTS); }
141                        mailbox = accountConfig.getSentMailbox();
142                        if(mailbox != null) { mailbox.setType(MailboxNode.TYPE_SENT); }
143                }
144
145        for(int i=0; i<accounts.length; i++) {
146            reSortMailboxes(accounts[i].getRootMailbox());
147        }
148        }
149
150    private static void clearMailboxTypes(MailboxNode currentNode) {
151        if(currentNode == null) { return; }
152        if(currentNode.getType() != MailboxNode.TYPE_INBOX) {
153            currentNode.setType(MailboxNode.TYPE_NORMAL);
154        }
155        MailboxNode[] children = currentNode.getMailboxes();
156        if(children != null && children.length > 0) {
157            for(int i=0; i<children.length; i++) {
158                clearMailboxTypes(children[i]);
159            }
160        }
161    }
162   
163    private static void reSortMailboxes(MailboxNode currentNode) {
164        if(currentNode == null) { return; }
165        currentNode.reSort();
166        MailboxNode[] children = currentNode.getMailboxes();
167        if(children != null && children.length > 0) {
168            for(int i=0; i<children.length; i++) {
169                reSortMailboxes(children[i]);
170            }
171        }
172    }
173       
174        /**
175         * First time initialization sequence, should only be invoked on
176         * application startup.
177         */
178        public static void initialize() {
179                MailManager mailManager = MailManager.getInstance();
180                mailManager.refreshMailboxTypes();
181        }
182       
183        /**
184         * Gets the mail manager instance.
185         *
186         * @return The instance.
187         */
188        public static synchronized MailManager getInstance() {
189                if(instance == null) {
190                        instance = new MailManager();
191                }
192                return instance;
193        }
194
195        /**
196         * Gets the system listener used by the mail manager to respond to
197         * system events.
198         *
199         * @return The mail manager's system listener.
200         */
201    public SystemListener getSystemListener() {
202        return systemListener;
203    }
204
205    /**
206     * This method should be invoked as soon as the application is completely
207     * initialized, and the home screen has been displayed.  It is used to
208     * trigger any operations that happen automatically after startup.
209     */
210    public synchronized void startupComplete() {
211        // Make sure this method can only be called once
212        if(startupComplete) { return; }
213       
214        NetworkAccountNode[] networkAccounts = mailRootNode.getNetworkAccounts();
215        for(int i=0; i<networkAccounts.length; i++) {
216            AccountConfig accountConfig = networkAccounts[i].getAccountConfig();
217            int setting = accountConfig.getRefreshOnStartup();
218
219            boolean refreshTriggered = false;
220            if(accountConfig.getRefreshOnStartup() >= AccountConfig.REFRESH_ON_STARTUP_STATUS) {
221                //TODO: Consider handling folder refresh on missing-folder errors
222                //TODO: Making the GUI always show a server-side value for message counts will clear up side-effects
223                networkAccounts[i].refreshMailboxStatus();
224                refreshTriggered = true;
225            }
226            if(setting == AccountConfig.REFRESH_ON_STARTUP_HEADERS) {
227                networkAccounts[i].triggerAutomaticRefresh(true);
228                refreshTriggered = true;
229            }
230           
231            // If a refresh was not triggered on startup, but polling is
232            // enabled, then make sure the polling thread is still started.
233            if(!refreshTriggered && accountConfig.getRefreshFrequency() > 0) {
234                networkAccounts[i].scheduleAutomaticRefresh();
235            }
236        }
237        startupComplete = true;
238    }
239   
240    private void handleSystemPowerUp() {
241        if(!startupComplete) { return; }
242        NetworkAccountNode[] accounts = mailRootNode.getNetworkAccounts();
243        for(int i=0; i<accounts.length; i++) {
244            AccountConfig accountConfig = accounts[i].getAccountConfig();
245            if(accountConfig.getRefreshFrequency() > 0) {
246                accounts[i].scheduleAutomaticRefresh();
247            }
248        }
249    }
250   
251    private void handleSystemPowerOff() {
252        NetworkAccountNode[] accounts = mailRootNode.getNetworkAccounts();
253        for(int i=0; i<accounts.length; i++) {
254            if(accounts[i].getStatus() == AccountNode.STATUS_ONLINE) {
255                accounts[i].requestDisconnect(false);
256            }
257        }
258    }
259       
260        /**
261         * Gets the mail root node.
262         * Also performs any necessary initialization on
263         * the first call.
264         *
265         * @return Mail root node.
266         */
267        public synchronized MailRootNode getMailRootNode() {
268                return mailRootNode;
269        }
270
271        /**
272         * Gets the outbox node within the local account.
273         *
274         * @return Outbox node.
275         */
276        public OutboxMailboxNode getOutboxMailboxNode() {
277                return outboxMailboxNode;
278        }
279
280        /**
281         * Clear the contents of the folder message cache.
282         */
283        public void clearFolderMessageCache() {
284            folderMessageCache.clear();
285            if(outboxMailboxNode != null) {
286                outboxMailboxNode.clearMessages();
287            }
288            System.gc();
289        }
290       
291        private void mailSettings_MailSettingsSaved(MailSettingsEvent e) {
292        // This logic is rather crude, and will trigger a full refresh
293        // under a wide variety of circumstances.  Its major intent is
294        // to avoid a refresh in a few situations where a minor
295        // configuration change affects future behavior and not
296        // current state.
297       
298        boolean shouldRefreshAccounts =
299            connectionListChanged(e) || accountChangeRequiringRefresh(e);
300       
301        // Trigger a refresh if appropriate
302        if(shouldRefreshAccounts) {
303            updateMailModelAccountList();
304            refreshMailboxTypes();
305           
306            // Notify any listeners
307            fireMailConfigurationChanged();
308        }
309       
310        if((e.getGlobalChange() & GlobalConfig.CHANGE_TYPE_DATA) != 0) {
311            mailRootNode.getLocalAccount().refreshMailboxes();
312        }
313        }
314
315    private boolean connectionListChanged(MailSettingsEvent e) {
316        int listChange = e.getListChange();
317        return (listChange & MailSettingsEvent.LIST_CHANGED_ACCOUNT) != 0
318                || (listChange & MailSettingsEvent.LIST_CHANGED_OUTGOING) != 0;
319    }
320       
321    private boolean accountChangeRequiringRefresh(MailSettingsEvent e) {
322        boolean refreshRequired = false;
323        int num = mailSettings.getNumAccounts();
324        for(int i=0; i<num; i++) {
325            AccountConfig accountConfig = mailSettings.getAccountConfig(i);
326            int accountChange = e.getConfigChange(accountConfig);
327            if((accountChange & AccountConfig.CHANGE_TYPE_NAME) != 0
328                    || (accountChange & AccountConfig.CHANGE_TYPE_MAILBOXES) != 0
329                    || (accountChange & AccountConfig.CHANGE_TYPE_OUTGOING) != 0) {
330                refreshRequired = true;
331                break;
332            }
333            OutgoingConfig outgoingConfig = accountConfig.getOutgoingConfig();
334            if(outgoingConfig != null
335                    && (e.getConfigChange(outgoingConfig) & OutgoingConfig.CHANGE_TYPE_CONNECTION) != 0) {
336                refreshRequired = true;
337                break;
338            }
339        }
340        return refreshRequired;
341    }
342   
343    /**
344     * Called when the account configuration has changed,
345     * to cause the mail model to update its account list.
346     */
347    private synchronized void updateMailModelAccountList() {
348        // Build the new account list from the configuration and
349        // the existing account nodes.  This works by checking to see
350        // if an account already exists, and only creating new nodes
351        // for new accounts.
352        NetworkAccountNode[] existingAccounts = mailRootNode.getNetworkAccounts();
353        NetworkAccountNode[] newAccounts = getNewNetworkAccountNodes();
354
355        // Remove and replace all account nodes from the root node.
356        // This approach is taken to preserve any ordering which may
357        // have been changed in the configuration.
358        mailRootNode.removeAccounts(existingAccounts);
359        mailRootNode.addAccounts(newAccounts);
360
361        // Clear deleted accounts from the MailFactory and persistent storage
362        clearDeletedAccounts(existingAccounts, newAccounts);
363
364        // Get the newly updated account list, and determine whether
365        // we need to update any mail senders.
366        NetworkAccountNode[] updatedAccounts = mailRootNode.getNetworkAccounts();
367        updateAccountMailSenders(updatedAccounts);
368
369        //TODO: Clear deleted senders from the MailFactory
370    }
371
372    private NetworkAccountNode[] getNewNetworkAccountNodes() {
373            Vector newAccounts = new Vector();
374
375            int num = mailSettings.getNumAccounts();
376            for(int i=0; i<num; i++) {
377                AccountConfig accountConfig = mailSettings.getAccountConfig(i);
378                NetworkAccountNode existingAccountNode = mailRootNode.findAccountForConfig(accountConfig);
379
380                if(existingAccountNode != null) {
381                    newAccounts.addElement(existingAccountNode);
382                }
383                else {
384                    AccountNode newAccountNode = new NetworkAccountNode(
385                            new NetworkMailStoreServices(
386                                    (NetworkMailStore) MailFactory.createMailStore(accountConfig),
387                                    folderMessageCache));
388                    newAccountNode.load();
389                    newAccounts.addElement(newAccountNode);
390                }
391            }
392            NetworkAccountNode[] newAccountsArray = new NetworkAccountNode[newAccounts.size()];
393            newAccounts.copyInto(newAccountsArray);
394            return newAccountsArray;
395        }
396       
397    private void clearDeletedAccounts(NetworkAccountNode[] existingAccounts, NetworkAccountNode[] newAccounts) {
398        for(int i=0; i<existingAccounts.length; i++) {
399            boolean accountDeleted = true;
400            for(int j=0; j<newAccounts.length; j++) {
401                NetworkAccountNode newAccount = newAccounts[j];
402                if(newAccount == existingAccounts[i]) {
403                    accountDeleted = false;
404                    break;
405                }
406            }
407            if(accountDeleted) {
408                existingAccounts[i].removeSavedData();
409                MailFactory.clearMailStore(existingAccounts[i].getAccountConfig());
410            }
411        }
412    }
413
414    private void updateAccountMailSenders(NetworkAccountNode[] updatedAccounts) {
415        for(int i=0; i<updatedAccounts.length; i++) {
416            NetworkAccountNode networkAccount = updatedAccounts[i];
417            AbstractMailSender mailSender = networkAccount.getMailSender();
418            OutgoingConfig outgoingConfig = networkAccount.getAccountConfig().getOutgoingConfig();
419            if(outgoingConfig == null) {
420                if(mailSender != null) {
421                    mailSender.shutdown(false);
422                }
423                networkAccount.setMailSender(null);
424            }
425            else if((mailSender instanceof NetworkMailSender
426                    &&((NetworkMailSender)mailSender).getOutgoingConfig() != outgoingConfig)) {
427                mailSender.shutdown(false);
428                networkAccount.setMailSender(MailFactory.createMailSender(networkAccount.getAccountConfig().getOutgoingConfig()));
429            }
430            else if(mailSender == null) {
431                networkAccount.setMailSender(MailFactory.createMailSender(networkAccount.getAccountConfig().getOutgoingConfig()));
432            }
433        }
434    }
435
436        /**
437         * Handle connection state changes by updating the
438         * appropriate account nodes.
439         *
440         * @param e Event data.
441         */
442        private void mailConnectionManager_MailConnectionStateChanged(MailConnectionStateEvent e) {
443            if(e.getConnectionConfig() instanceof AccountConfig) {
444                // Find the account node associated with this event
445                AccountNode matchingAccount = mailRootNode.findAccountForConfig(
446                        (AccountConfig)e.getConnectionConfig());
447               
448                // Update account state
449                if(matchingAccount != null) {
450                        int state = e.getState();
451                        if(state == MailConnectionStateEvent.STATE_CONNECTED) {
452                                matchingAccount.setStatus(AccountNode.STATUS_ONLINE);
453                        }
454                        else if(state == MailConnectionStateEvent.STATE_DISCONNECTED) {
455                                matchingAccount.setStatus(AccountNode.STATUS_OFFLINE);
456                        }
457                }
458            }
459        }
460       
461        /**
462     * Adds a <tt>MailManagerListener</tt> to the mail manager.
463     *
464     * @param l The <tt>MailManagerListener</tt> to be added.
465     */
466    public void addMailManagerListener(MailManagerListener l) {
467        listenerList.add(MailManagerListener.class, l);
468    }
469
470    /**
471     * Removes a <tt>MailManagerListener</tt> from the mail manager.
472     *
473     * @param l The <tt>MailManagerListener</tt> to be removed.
474     */
475    public void removeMailManagerListener(MailManagerListener l) {
476        listenerList.remove(MailManagerListener.class, l);
477    }
478   
479    /**
480     * Returns an array of all <tt>MailManagerListener</tt>s
481     * that have been added to this mail manager.
482     *
483     * @return All the <tt>MailManagerListener</tt>s that have been added,
484     * or an empty array if no listeners have been added.
485     */
486    public MailManagerListener[] getMailManagerListeners() {
487        return (MailManagerListener[])listenerList.getListeners(MailManagerListener.class);
488    }
489   
490    /**
491     * Notifies all registered <tt>MailManagerListener</tt>s that
492     * the mail system configuration has changed.
493     */
494    private void fireMailConfigurationChanged() {
495        Object[] listeners = listenerList.getListeners(MailManagerListener.class);
496        MailManagerEvent e = null;
497        for(int i=0; i<listeners.length; i++) {
498            if(e == null) {
499                e = new MailManagerEvent(this);
500            }
501            ((MailManagerListener)listeners[i]).mailConfigurationChanged(e);
502        }
503    }
504}
Note: See TracBrowser for help on using the repository browser.