summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/nsMsgBiffManager.cpp
blob: 9d990a30bd3f2d2beea582ec6e872d183e5b9cf0 (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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#include "nsMsgBiffManager.h"
#include "nsIMsgAccountManager.h"
#include "nsStatusBarBiffManager.h"
#include "nsCOMArray.h"
#include "mozilla/Logging.h"
#include "nspr.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIObserverService.h"
#include "nsComponentManagerUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsMsgUtils.h"
#include "nsITimer.h"
#include "mozilla/Services.h"

#define PREF_BIFF_JITTER "mail.biff.add_interval_jitter"

#define NS_STATUSBARBIFFMANAGER_CID                  \
  {                                                  \
    0x7f9a9fb0, 0x4161, 0x11d4, {                    \
      0x98, 0x76, 0x00, 0xc0, 0x4f, 0xa0, 0xd2, 0xa6 \
    }                                                \
  }
static NS_DEFINE_CID(kStatusBarBiffManagerCID, NS_STATUSBARBIFFMANAGER_CID);

static mozilla::LazyLogModule MsgBiffLogModule("MsgBiff");

NS_IMPL_ISUPPORTS(nsMsgBiffManager, nsIMsgBiffManager,
                  nsIIncomingServerListener, nsIObserver,
                  nsISupportsWeakReference)

void OnBiffTimer(nsITimer* timer, void* aBiffManager) {
  nsMsgBiffManager* biffManager = (nsMsgBiffManager*)aBiffManager;
  biffManager->PerformBiff();
}

nsMsgBiffManager::nsMsgBiffManager() {
  mHaveShutdown = false;
  mInited = false;
}

nsMsgBiffManager::~nsMsgBiffManager() {
  if (mBiffTimer) mBiffTimer->Cancel();

  if (!mHaveShutdown) Shutdown();

  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();
  if (observerService) {
    observerService->RemoveObserver(this, "wake_notification");
    observerService->RemoveObserver(this, "sleep_notification");
  }
}

NS_IMETHODIMP nsMsgBiffManager::Init() {
  if (mInited) return NS_OK;

  mInited = true;
  nsresult rv;

  nsCOMPtr<nsIMsgAccountManager> accountManager =
      do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
  if (NS_SUCCEEDED(rv)) accountManager->AddIncomingServerListener(this);

  // in turbo mode on profile change we don't need to do anything below this
  if (mHaveShutdown) {
    mHaveShutdown = false;
    return NS_OK;
  }

  // Ensure status bar biff service has started
  nsCOMPtr<nsIFolderListener> statusBarBiffService =
      do_GetService(kStatusBarBiffManagerCID, &rv);

  nsCOMPtr<nsIObserverService> observerService =
      mozilla::services::GetObserverService();
  if (observerService) {
    observerService->AddObserver(this, "sleep_notification", true);
    observerService->AddObserver(this, "wake_notification", true);
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgBiffManager::Shutdown() {
  if (mBiffTimer) {
    mBiffTimer->Cancel();
    mBiffTimer = nullptr;
  }

  nsresult rv;
  nsCOMPtr<nsIMsgAccountManager> accountManager =
      do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
  if (NS_SUCCEEDED(rv)) accountManager->RemoveIncomingServerListener(this);

  mHaveShutdown = true;
  mInited = false;
  return NS_OK;
}

NS_IMETHODIMP nsMsgBiffManager::Observe(nsISupports* aSubject,
                                        const char* aTopic,
                                        const char16_t* someData) {
  if (!strcmp(aTopic, "sleep_notification") && mBiffTimer) {
    mBiffTimer->Cancel();
    mBiffTimer = nullptr;
  } else if (!strcmp(aTopic, "wake_notification")) {
    // wait 10 seconds after waking up to start biffing again.
    nsresult rv = NS_NewTimerWithFuncCallback(
        getter_AddRefs(mBiffTimer), OnBiffTimer, (void*)this, 10000,
        nsITimer::TYPE_ONE_SHOT, "nsMsgBiffManager::OnBiffTimer", nullptr);
    if (NS_FAILED(rv)) {
      NS_WARNING("Could not start mBiffTimer timer");
    }
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgBiffManager::AddServerBiff(nsIMsgIncomingServer* server) {
  NS_ENSURE_ARG_POINTER(server);

  int32_t biffMinutes;

  nsresult rv = server->GetBiffMinutes(&biffMinutes);
  NS_ENSURE_SUCCESS(rv, rv);

  // Don't add if biffMinutes isn't > 0
  if (biffMinutes > 0) {
    int32_t serverIndex = FindServer(server);
    // Only add it if it hasn't been added already.
    if (serverIndex == -1) {
      nsBiffEntry biffEntry;
      biffEntry.server = server;
      rv = SetNextBiffTime(biffEntry, PR_Now());
      NS_ENSURE_SUCCESS(rv, rv);

      AddBiffEntry(biffEntry);
      SetupNextBiff();
    }
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgBiffManager::RemoveServerBiff(nsIMsgIncomingServer* server) {
  int32_t pos = FindServer(server);
  if (pos != -1) mBiffArray.RemoveElementAt(pos);

  // Should probably reset biff time if this was the server that gets biffed
  // next.
  return NS_OK;
}

NS_IMETHODIMP nsMsgBiffManager::ForceBiff(nsIMsgIncomingServer* server) {
  return NS_OK;
}

NS_IMETHODIMP nsMsgBiffManager::ForceBiffAll() { return NS_OK; }

NS_IMETHODIMP nsMsgBiffManager::OnServerLoaded(nsIMsgIncomingServer* server) {
  NS_ENSURE_ARG_POINTER(server);

  bool doBiff = false;
  nsresult rv = server->GetDoBiff(&doBiff);

  if (NS_SUCCEEDED(rv) && doBiff) rv = AddServerBiff(server);

  return rv;
}

NS_IMETHODIMP nsMsgBiffManager::OnServerUnloaded(nsIMsgIncomingServer* server) {
  return RemoveServerBiff(server);
}

NS_IMETHODIMP nsMsgBiffManager::OnServerChanged(nsIMsgIncomingServer* server) {
  // nothing required.  If the hostname or username changed
  // the next time biff fires, we'll ping the right server
  return NS_OK;
}

int32_t nsMsgBiffManager::FindServer(nsIMsgIncomingServer* server) {
  uint32_t count = mBiffArray.Length();
  for (uint32_t i = 0; i < count; i++) {
    if (server == mBiffArray[i].server.get()) return i;
  }
  return -1;
}

nsresult nsMsgBiffManager::AddBiffEntry(nsBiffEntry& biffEntry) {
  uint32_t i;
  uint32_t count = mBiffArray.Length();
  for (i = 0; i < count; i++) {
    if (biffEntry.nextBiffTime < mBiffArray[i].nextBiffTime) break;
  }
  MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
          ("inserting biff entry at %d", i));
  mBiffArray.InsertElementAt(i, biffEntry);
  return NS_OK;
}

nsresult nsMsgBiffManager::SetNextBiffTime(nsBiffEntry& biffEntry,
                                           PRTime currentTime) {
  nsIMsgIncomingServer* server = biffEntry.server;
  NS_ENSURE_TRUE(server, NS_ERROR_FAILURE);

  int32_t biffInterval;
  nsresult rv = server->GetBiffMinutes(&biffInterval);
  NS_ENSURE_SUCCESS(rv, rv);

  // Add biffInterval, converted in microseconds, to current time.
  // Force 64-bit multiplication.
  PRTime chosenTimeInterval = biffInterval * 60000000LL;
  biffEntry.nextBiffTime = currentTime + chosenTimeInterval;

  // Check if we should jitter.
  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
  if (prefs) {
    bool shouldUseBiffJitter = false;
    prefs->GetBoolPref(PREF_BIFF_JITTER, &shouldUseBiffJitter);
    if (shouldUseBiffJitter) {
      // Calculate a jitter of +/-5% on chosenTimeInterval
      // - minimum 1 second (to avoid a modulo with 0)
      // - maximum 30 seconds (to avoid problems when biffInterval is very
      // large)
      int64_t jitter = (int64_t)(0.05 * (int64_t)chosenTimeInterval);
      jitter =
          std::max<int64_t>(1000000LL, std::min<int64_t>(jitter, 30000000LL));
      jitter = ((rand() % 2) ? 1 : -1) * (rand() % jitter);

      biffEntry.nextBiffTime += jitter;
    }
  }

  return NS_OK;
}

nsresult nsMsgBiffManager::SetupNextBiff() {
  if (mBiffArray.Length() > 0) {
    // Get the next biff entry
    const nsBiffEntry& biffEntry = mBiffArray[0];
    PRTime currentTime = PR_Now();
    int64_t biffDelay;
    int64_t ms(1000);

    if (currentTime > biffEntry.nextBiffTime) {
      // Let's wait 30 seconds before firing biff again
      biffDelay = 30 * PR_USEC_PER_SEC;
    } else
      biffDelay = biffEntry.nextBiffTime - currentTime;

    // Convert biffDelay into milliseconds
    int64_t timeInMS = biffDelay / ms;
    uint32_t timeInMSUint32 = (uint32_t)timeInMS;

    // Can't currently reset a timer when it's in the process of
    // calling Notify. So, just release the timer here and create a new one.
    if (mBiffTimer) mBiffTimer->Cancel();

    MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
            ("setting %d timer", timeInMSUint32));

    nsresult rv = NS_NewTimerWithFuncCallback(
        getter_AddRefs(mBiffTimer), OnBiffTimer, (void*)this, timeInMSUint32,
        nsITimer::TYPE_ONE_SHOT, "nsMsgBiffManager::OnBiffTimer", nullptr);
    if (NS_FAILED(rv)) {
      NS_WARNING("Could not start mBiffTimer timer");
    }
  }
  return NS_OK;
}

// This is the function that does a biff on all of the servers whose time it is
// to biff.
nsresult nsMsgBiffManager::PerformBiff() {
  PRTime currentTime = PR_Now();
  nsCOMArray<nsIMsgFolder> targetFolders;
  MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("performing biffs"));

  uint32_t count = mBiffArray.Length();
  for (uint32_t i = 0; i < count; i++) {
    // Take a copy of the entry rather than the a reference so that we can
    // remove and add if necessary, but keep the references and memory alive.
    nsBiffEntry current = mBiffArray[i];
    if (current.nextBiffTime < currentTime) {
      bool serverBusy = false;
      bool serverRequiresPassword = true;
      bool passwordPromptRequired;

      current.server->GetPasswordPromptRequired(&passwordPromptRequired);
      current.server->GetServerBusy(&serverBusy);
      current.server->GetServerRequiresPasswordForBiff(&serverRequiresPassword);
      // find the dest folder we're actually downloading to...
      nsCOMPtr<nsIMsgFolder> rootMsgFolder;
      current.server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
      int32_t targetFolderIndex = targetFolders.IndexOfObject(rootMsgFolder);
      if (targetFolderIndex == kNotFound)
        targetFolders.AppendObject(rootMsgFolder);

      // so if we need to be authenticated to biff, check that we are
      // (since we don't want to prompt the user for password UI)
      // and make sure the server isn't already in the middle of downloading
      // new messages
      if (!serverBusy && (!serverRequiresPassword || !passwordPromptRequired) &&
          targetFolderIndex == kNotFound) {
        nsCString serverKey;
        current.server->GetKey(serverKey);
        nsresult rv = current.server->PerformBiff(nullptr);
        MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
                ("biffing server %s rv = %" PRIx32, serverKey.get(),
                 static_cast<uint32_t>(rv)));
      } else {
        MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
                ("not biffing server serverBusy = %d requirespassword = %d "
                 "password prompt required = %d targetFolderIndex = %d",
                 serverBusy, serverRequiresPassword, passwordPromptRequired,
                 targetFolderIndex));
      }
      // if we didn't do this server because the destination server was already
      // being biffed into, leave this server in the biff array so it will fire
      // next.
      if (targetFolderIndex == kNotFound) {
        mBiffArray.RemoveElementAt(i);
        i--;  // Because we removed it we need to look at the one that just
              // moved up.
        SetNextBiffTime(current, currentTime);
        AddBiffEntry(current);
      }
#ifdef DEBUG_David_Bienvenu
      else
        printf("dest account performing biff\n");
#endif
    } else
      // since we're in biff order, there's no reason to keep checking
      break;
  }
  SetupNextBiff();
  return NS_OK;
}