summaryrefslogtreecommitdiffstats
path: root/mailnews/db/gloda/modules/utils.js
blob: 2a095c42788e05a1e1bb5aba6859ebed9d66a514 (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
/* 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.EXPORTED_SYMBOLS = ['GlodaUtils'];

var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
var Cu = Components.utils;

Cu.import("resource:///modules/mailServices.js");

/**
 * @namespace A holding place for logic that is not gloda-specific and should
 *  reside elsewhere.
 */
var GlodaUtils = {

  /**
   * This Regexp is super-complicated and used at least in two different parts of
   * the code, so let's expose it from one single location.
   */
  PART_RE: new RegExp("^[^?]+\\?(?:/;section=\\d+\\?)?(?:[^&]+&)*part=([^&]+)(?:&[^&]+)*$"),

  deMime: function gloda_utils_deMime(aString) {
    return MailServices.mimeConverter.decodeMimeHeader(aString, null, false, true);
  },

  _headerParser: MailServices.headerParser,

  /**
   * Parses an RFC 2822 list of e-mail addresses and returns an object with
   *  4 attributes, as described below.  We will use the example of the user
   *  passing an argument of '"Bob Smith" <bob@example.com>'.
   *
   * This method (by way of nsIMsgHeaderParser) takes care of decoding mime
   *  headers, but is not aware of folder-level character set overrides.
   *
   * count: the number of addresses parsed. (ex: 1)
   * addresses: a list of e-mail addresses (ex: ["bob@example.com"])
   * names: a list of names (ex: ["Bob Smith"])
   * fullAddresses: aka the list of name and e-mail together (ex: ['"Bob Smith"
   *  <bob@example.com>']).
   *
   * This method is a convenience wrapper around nsIMsgHeaderParser.
   */
  parseMailAddresses: function gloda_utils_parseMailAddresses(aMailAddresses) {
    let addresses = {}, names = {}, fullAddresses = {};
    this._headerParser.parseHeadersWithArray(aMailAddresses, addresses,
                                             names, fullAddresses);
    return {names: names.value, addresses: addresses.value,
            fullAddresses: fullAddresses.value,
            count: names.value.length};
  },

  /**
   * MD5 hash a string and return the hex-string result. Impl from nsICryptoHash
   *  docs.
   */
  md5HashString: function gloda_utils_md5hash(aString) {
    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                    createInstance(Ci.nsIScriptableUnicodeConverter);
    let trash = {};
    converter.charset = "UTF-8";
    let data = converter.convertToByteArray(aString, trash);

    let hasher = Cc['@mozilla.org/security/hash;1'].
                 createInstance(Ci.nsICryptoHash);
    hasher.init(Ci.nsICryptoHash.MD5);
    hasher.update(data, data.length);
    let hash = hasher.finish(false);

     // return the two-digit hexadecimal code for a byte
    function toHexString(charCode) {
      return ("0" + charCode.toString(16)).slice(-2);
    }

    // convert the binary hash data to a hex string.
    let hex = Object.keys(hash).map(i => toHexString(hash.charCodeAt(i)));
    return hex.join("");
  },

  getCardForEmail: function gloda_utils_getCardForEmail(aAddress) {
    // search through all of our local address books looking for a match.
    let enumerator = MailServices.ab.directories;
    let cardForEmailAddress;
    let addrbook;
    while (!cardForEmailAddress && enumerator.hasMoreElements())
    {
      addrbook = enumerator.getNext().QueryInterface(Ci.nsIAbDirectory);
      try
      {
        cardForEmailAddress = addrbook.cardForEmailAddress(aAddress);
        if (cardForEmailAddress)
          return cardForEmailAddress;
      } catch (ex) {}
    }

    return null;
  },

  _FORCE_GC_AFTER_NUM_HEADERS: 4096,
  _headersSeen: 0,
  /**
   * As |forceGarbageCollection| says, once XPConnect sees a header, it likes
   *  to hold onto that reference.  This method is used to track the number of
   *  headers we have seen and force a GC when we have to.
   *
   * Ideally the indexer's idle-biased GC mechanism would take care of all the
   *  GC; we are just a failsafe to make sure that our memory usage is bounded
   *  based on the number of headers we have seen rather than just time.
   *  Since holding onto headers can keep databases around too, this also
   *  helps avoid keeping file handles open, etc.
   *
   * |forceGarbageCollection| will zero our tracking variable when a GC happens
   *  so we are informed by the indexer's GC triggering.
   *
   * And of course, we don't want to trigger collections willy nilly because
   *  they have a cost even if there is no garbage.
   *
   * @param aNumHeadersSeen The number of headers code has seen.  A granularity
   *     of hundreds of messages should be fine.
   */
  considerHeaderBasedGC: function(aNumHeadersSeen) {
    this._headersSeen += aNumHeadersSeen;
    if (this._headersSeen >= this._FORCE_GC_AFTER_NUM_HEADERS)
      this.forceGarbageCollection();
  },

  /**
   * Force a garbage-collection sweep.  Gloda has to force garbage collection
   *  periodically because XPConnect's XPCJSRuntime::DeferredRelease mechanism
   *  can end up holding onto a ridiculously high number of XPConnect objects in
   *  between normal garbage collections.  This has mainly posed a problem
   *  because nsAutolock is a jerk in DEBUG builds in 1.9.1, but in theory this
   *  also helps us even out our memory usage.
   * We also are starting to do this more to try and keep the garbage collection
   *  durations acceptable.  We intentionally avoid triggering the cycle
   *  collector in those cases, as we do presume a non-trivial fixed cost for
   *  cycle collection.  (And really all we want is XPConnect to not be a jerk.)
   * This method exists mainly to centralize our GC activities and because if
   *  we do start involving the cycle collector, that is a non-trivial block of
   *  code to copy-and-paste all over the place (at least in a module).
   *
   * @param aCycleCollecting Do we need the cycle collector to run?  Currently
   *     unused / unimplemented, but we would use
   *     nsIDOMWindowUtils.garbageCollect() to do so.
   */
  forceGarbageCollection:
      function gloda_utils_garbageCollection(aCycleCollecting) {
    Cu.forceGC();
    this._headersSeen = 0;
  }
};