summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/FolderLookupService.jsm
blob: 9530dc6e6ef48f7f99e9e2e17bc12fbfb4b87d92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module implements the folder lookup service (nsIFolderLookupService).
 */

"use strict";

var EXPORTED_SYMBOLS = ["FolderLookupService"];

// This ensures that the service is only created once.
var gCreated = false;

/**
 * FolderLookupService maintains an index of folders and provides
 * lookup by folder URI.
 *
 * @class
 */
function FolderLookupService() {
  if (gCreated) {
    throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED);
  }
  this._map = new Map();
  gCreated = true;
}

FolderLookupService.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIFolderLookupService"]),

  /**
   * Fetch the folder corresponding to the given URI.
   * Will only return folders which already exist and have a parent. If this
   * not the case then null is returned.
   *
   * @param {string} uri - URI of folder to get.
   * @returns {nsIMsgFolder|null}
   */
  getFolderForURL(uri) {
    let folder = this._getExisting(uri);

    if (folder && !this._isValidFolder(folder)) {
      folder = null; // no dangling folders!
    }
    return folder;
  },

  /**
   * Fetch the folder corresponding to the given URI, creating it if it does
   * not exist. If the folder is created, it will be a "dangling" folder,
   * without a parent and not part of a normal folder hierarchy.
   * A lot of code relies on this behaviour, but for new code this
   * call should probably be avoided.
   *
   * @param {string} uri - URI of folder to get.
   * @returns {nsIMsgFolder}
   */
  getOrCreateFolderForURL(uri) {
    let folder = this._getExisting(uri);
    if (folder) {
      return folder;
    }

    // Create new folder.

    // Check that uri has an active scheme, in case this folder is from
    // an extension that is currently disabled or hasn't started up yet.
    let schemeMatch = uri.match(/^([-+.\w]+):/);
    if (!schemeMatch) {
      return null;
    }
    let scheme = schemeMatch[1];
    let contractID = "@mozilla.org/mail/folder-factory;1?name=" + scheme;
    if (!(contractID in Cc)) {
      console.error(
        "getOrCreateFolderForURL: factory not registered for " + uri
      );
      return null;
    }

    let factory = Components.manager.getClassObject(
      Cc[contractID],
      Ci.nsIFactory
    );
    if (!factory) {
      console.error(
        "getOrCreateFolderForURL: failed to get factory for " + uri
      );
      return null;
    }

    folder = factory.createInstance(Ci.nsIMsgFolder);
    if (folder) {
      folder.Init(uri);
      // Add the new folder to our map. Store a weak reference instead, so that
      // the folder can be closed when necessary.
      let weakRef = folder
        .QueryInterface(Ci.nsISupportsWeakReference)
        .GetWeakReference();
      this._map.set(uri, weakRef);
    }

    return folder;
  },

  /**
   * Set pretty name again from original name on all folders,
   * typically used when locale changes.
   */
  setPrettyNameFromOriginalAllFolders() {
    for (const val of this._map.values()) {
      try {
        let folder = val.QueryReferent(Ci.nsIMsgFolder);
        folder.setPrettyNameFromOriginal();
      } catch (e) {}
    }
  },

  // "private" stuff starts here.

  /**
   * Internal helper to find a folder (which may or may not be dangling).
   *
   * @param {string} uri - URI of folder to look up.
   *
   * @returns {nsIMsgFolder|null} - The folder, if in the index, else null.
   */
  _getExisting(uri) {
    let folder = null;
    // already created?
    if (this._map.has(uri)) {
      try {
        folder = this._map.get(uri).QueryReferent(Ci.nsIMsgFolder);
      } catch (e) {
        // The object was deleted, so we can drop it.
        this._map.delete(uri);
      }
    }
    return folder;
  },

  /**
   * Internal helper function to test if a folder is dangling or parented.
   * Because we can return folders that don't exist, and we may be working
   * with a deleted folder but we're still holding on to the reference. For
   * valid folders, one of two scenarios is true: either the folder has a parent
   * (the deletion code clears the parent to indicate its nonvalidity), or the
   * folder is a root folder of some server. Getting the root folder may throw
   * an exception if we attempted to create a server that doesn't exist, so we
   * need to guard for that error.
   *
   * @returns {boolean} - true if folder valid (and parented).
   */
  _isValidFolder(folder) {
    try {
      return folder.parent != null || folder.rootFolder == folder;
    } catch (e) {
      return false;
    }
  },
};