Mozilla/mozilla/modules/progress/src/nsTopProgressManager.cpp
gerv%gerv.net 8b69962ee3 Bug 236613: change to MPL/LGPL/GPL tri-license.
git-svn-id: svn://10.0.0.236/trunk@155500 18797224-902f-48f8-a5cc-f745e15eee43
2004-04-25 21:07:34 +00:00

712 lines
19 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsTopProgressManager.h"
#include "nsTransfer.h"
#include "xp.h" // for FE_* callbacks
#include "xpgetstr.h"
#include "prprf.h"
#include "prmem.h"
#include "plstr.h"
#define OBJECT_TABLE_INIT_SIZE 32
////////////////////////////////////////////////////////////////////////
//
// Debugging garbage
//
//
#if 0 /* defined(DEBUG) */
#define TRACE_PROGRESS(args) pm_TraceProgress args
static void
pm_TraceProgress(const char* fmtstr, ...)
{
char buf[256];
va_list ap;
va_start(ap, fmtstr);
PR_vsnprintf(buf, sizeof(buf), fmtstr, ap);
va_end(ap);
#if defined(XP_WIN)
OutputDebugString(buf);
#elif defined(XP_UNIX)
#elif defined(XP_MAC)
#endif
}
#else /* defined(DEBUG) */
#define TRACE_PROGRESS(args)
#endif /* defined(DEBUG) */
////////////////////////////////////////////////////////////////////////
//
// Hash table allocation routines
//
//
static PR_CALLBACK void*
AllocTable(void* pool, PRSize size)
{
return PR_MALLOC(size);
}
static PR_CALLBACK void
FreeTable(void* pool, void* item)
{
PR_DELETE(item);
}
static PR_CALLBACK PLHashEntry*
AllocEntry(void* pool, const void* key)
{
return PR_NEW(PLHashEntry);
}
static PR_CALLBACK void
FreeEntry(void* pool, PLHashEntry* he, PRUintn flag)
{
if (flag == HT_FREE_VALUE) {
if (he->value) {
nsTransfer* obj = (nsTransfer*) he->value;
obj->Release();
}
}
else if (flag == HT_FREE_ENTRY) {
// we don't own the key, so leave it alone...
if (he->value) {
nsTransfer* obj = (nsTransfer*) he->value;
obj->Release();
}
PR_DELETE(he);
}
}
static PLHashAllocOps AllocOps = {
AllocTable,
FreeTable,
AllocEntry,
FreeEntry
};
static PLHashNumber
pm_HashURL(const void* key)
{
return (PLHashNumber) key;
}
static int
pm_CompareURLs(const void* v1, const void* v2)
{
return v1 == v2;
}
////////////////////////////////////////////////////////////////////////
//
// Hash table iterators
//
//
static PRIntn
pm_AggregateTransferInfo(PLHashEntry* he, PRIntn i, void* closure)
{
AggregateTransferInfo* info = (AggregateTransferInfo*) closure;
++(info->ObjectCount);
if (he->value) {
nsTransfer* transfer = (nsTransfer*) he->value;
if (transfer->IsComplete())
++(info->CompleteCount);
if (transfer->IsSuspended())
++(info->SuspendedCount);
info->MSecRemaining += transfer->GetMSecRemaining();
info->BytesReceived += transfer->GetBytesReceived();
info->ContentLength += transfer->GetContentLength();
if (! transfer->IsComplete() &&
transfer->GetContentLength() == transfer->GetBytesReceived())
++(info->UnknownLengthCount);
}
return HT_ENUMERATE_NEXT;
}
////////////////////////////////////////////////////////////////////////
//
// nsTopProgressManager
//
//
////////////////////////////////////////////////////////////////////////
nsTopProgressManager::nsTopProgressManager(MWContext* context)
: nsProgressManager(context),
fActualStart(PR_Now()),
fProgressBarStart(PR_Now()),
fDefaultStatus(NULL)
{
fURLs = PL_NewHashTable(OBJECT_TABLE_INIT_SIZE,
pm_HashURL,
pm_CompareURLs,
PL_CompareValues,
&AllocOps,
NULL);
// Start the progress manager
fTimeout = FE_SetTimeout(nsTopProgressManager::TimeoutCallback, (void*) this, 500);
PR_ASSERT(fTimeout);
// to avoid "strobe" mode...
fProgress = 1;
FE_SetProgressBarPercent(fContext, fProgress);
}
nsTopProgressManager::~nsTopProgressManager(void)
{
if (fDefaultStatus) {
PL_strfree(fDefaultStatus);
fDefaultStatus = NULL;
}
if (fURLs) {
PL_HashTableDestroy(fURLs);
fURLs = NULL;
}
if (fTimeout) {
FE_ClearTimeout(fTimeout);
fTimeout = NULL;
}
// XXX Needs to go to allxpstr.h
FE_Progress(fContext, "Done.");
}
////////////////////////////////////////////////////////////////////////
NS_IMETHODIMP
nsTopProgressManager::OnStartBinding(const URL_Struct* url)
{
PR_ASSERT(url);
if (! url)
return NS_ERROR_NULL_POINTER;
PR_ASSERT(fURLs);
if (! fURLs)
return NS_ERROR_NULL_POINTER;
TRACE_PROGRESS(("OnStartBinding(%s)\n", url->address));
PL_HashTableAdd(fURLs, url, new nsTransfer(url));
return NS_OK;
}
NS_IMETHODIMP
nsTopProgressManager::OnProgress(const URL_Struct* url,
PRUint32 bytesReceived,
PRUint32 contentLength)
{
// Some sanity checks...
PR_ASSERT(url);
if (! url)
return NS_ERROR_NULL_POINTER;
TRACE_PROGRESS(("OnProgress(%s, %ld, %ld)\n", url->address, bytesReceived, contentLength));
PR_ASSERT(fURLs);
if (! fURLs)
return NS_ERROR_NULL_POINTER;
nsTransfer* transfer = (nsTransfer*) PL_HashTableLookup(fURLs, url);
PR_ASSERT(transfer);
if (!transfer)
return NS_ERROR_NULL_POINTER;
transfer->SetProgress(bytesReceived, contentLength);
return NS_OK;
}
NS_IMETHODIMP
nsTopProgressManager::OnStatus(const URL_Struct* url, const char* message)
{
TRACE_PROGRESS(("OnStatus(%s, %s)\n", (url ? url->address : NULL), message));
// There are cases when transfer may be null, and that's ok.
if (url) {
PR_ASSERT(fURLs);
if (! fURLs)
return NS_ERROR_NULL_POINTER;
nsTransfer* transfer = (nsTransfer*) PL_HashTableLookup(fURLs, url);
PR_ASSERT(transfer);
if (transfer)
transfer->SetStatus(message);
}
if (fDefaultStatus)
PL_strfree(fDefaultStatus);
fDefaultStatus = PL_strdup(message);
return NS_OK;
}
NS_IMETHODIMP
nsTopProgressManager::OnSuspend(const URL_Struct* url)
{
PR_ASSERT(url);
if (! url)
return NS_ERROR_NULL_POINTER;
TRACE_PROGRESS(("OnSuspend(%s)\n", url->address));
PR_ASSERT(fURLs);
if (! fURLs)
return NS_ERROR_NULL_POINTER;
nsTransfer* transfer = (nsTransfer*) PL_HashTableLookup(fURLs, url);
PR_ASSERT(transfer);
if (!transfer)
return NS_ERROR_NULL_POINTER;
transfer->Suspend();
return NS_OK;
}
NS_IMETHODIMP
nsTopProgressManager::OnResume(const URL_Struct* url)
{
PR_ASSERT(url);
if (! url)
return NS_ERROR_NULL_POINTER;
TRACE_PROGRESS(("OnResume(%s)\n", url->address));
PR_ASSERT(fURLs);
if (! fURLs)
return NS_ERROR_NULL_POINTER;
nsTransfer* transfer = (nsTransfer*) PL_HashTableLookup(fURLs, url);
PR_ASSERT(transfer);
if (!transfer)
return NS_ERROR_NULL_POINTER;
transfer->Resume();
return NS_OK;
}
NS_IMETHODIMP
nsTopProgressManager::OnStopBinding(const URL_Struct* url,
PRInt32 status,
const char* message)
{
PR_ASSERT(url);
if (! url)
return NS_ERROR_NULL_POINTER;
TRACE_PROGRESS(("OnStatus(%s, %d, %s)\n", url->address, status, message));
PR_ASSERT(fURLs);
if (! fURLs)
return NS_ERROR_NULL_POINTER;
nsTransfer* transfer = (nsTransfer*) PL_HashTableLookup(fURLs, url);
PR_ASSERT(transfer);
if (!transfer)
return NS_ERROR_NULL_POINTER;
transfer->MarkComplete(status);
transfer->SetStatus(message);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////
void
nsTopProgressManager::TimeoutCallback(void* closure)
{
nsTopProgressManager* self = (nsTopProgressManager*) closure;
self->Tick();
}
void
nsTopProgressManager::Tick(void)
{
TRACE_PROGRESS(("nsProgressManager.Tick: aggregating information for active objects\n"));
AggregateTransferInfo info = { 0, 0, 0, 0, 0, 0, 0 };
PL_HashTableEnumerateEntries(fURLs, pm_AggregateTransferInfo, (void*) &info);
TRACE_PROGRESS(("nsProgressManager.Tick: %ld of %ld objects complete, "
"%ldms left, "
"%ld of %ld bytes xferred\n",
info.CompleteCount, info.ObjectCount,
info.MSecRemaining,
info.BytesReceived, info.ContentLength));
PR_ASSERT(info.ObjectCount > 0);
if (info.ObjectCount == 0)
return;
UpdateProgressBar(info);
UpdateStatusMessage(info);
// Check to see if we're done.
if (info.CompleteCount == info.ObjectCount) {
TRACE_PROGRESS(("Complete: %ld/%ld objects loaded\n",
info.CompleteCount,
info.ObjectCount));
// XXX needs to go to allxpstr.h
FE_Progress(fContext, " ");
PL_HashTableDestroy(fURLs);
fURLs = NULL;
fTimeout = NULL;
}
else {
// Reset the timeout to fire again...
fTimeout = FE_SetTimeout(nsTopProgressManager::TimeoutCallback,
(void*) this, 500);
}
}
void
nsTopProgressManager::UpdateProgressBar(AggregateTransferInfo& info)
{
if (info.MSecRemaining == 0)
return;
if (info.SuspendedCount > 0) {
// turn on the strobe...
FE_SetProgressBarPercent(fContext, 0);
return;
}
nsInt64 dt = nsTime(PR_Now()) - fProgressBarStart;
PRUint32 elapsed = dt / nsInt64((PRUint32) PR_USEC_PER_MSEC);
// Compute the percent complete, that is, elapsed / (elapsed + remaining)
double p = ((double) elapsed) / ((double) (elapsed + info.MSecRemaining));
PRUint32 pctComplete = (PRUint32) (100.0 * p);
#define MONOTONIC_PROGRESS_BAR
#if defined(MONOTONIC_PROGRESS_BAR)
// This hackery is a kludge to make the progress bar
// monotonically increase rather than slipping backwards as we
// discover that there's more content to download. It works by
// adjusting the progress manager's start time backwards to
// make the elapsed time (time we've waited so far) seem
// larger in proportion to the amount of time that appears to
// be left.
if (pctComplete < fProgress) {
PRUint32 newElapsed =
(PRUint32) ((p * ((double) info.MSecRemaining))
/ (1.0 - p));
PRInt32 dMSec = newElapsed - elapsed;
if (dMSec > 0)
fProgressBarStart -= nsInt64(dMSec * ((PRUint32) PR_USEC_PER_MSEC));
// Progress bar hasn't changed -- don't bother updating it.
return;
}
#endif
fProgress = pctComplete;
FE_SetProgressBarPercent(fContext, fProgress);
}
////////////////////////////////////////////////////////////////////////
//
// The bulk of the following code was pulled over from lib/xp/xp_thermo.c
//
#ifdef XP_MAC
#include "allxpstr.h"
#else
PR_BEGIN_EXTERN_C
extern int XP_THERMO_BYTE_FORMAT;
extern int XP_THERMO_KBYTE_FORMAT;
extern int XP_THERMO_HOURS_FORMAT;
extern int XP_THERMO_MINUTES_FORMAT;
extern int XP_THERMO_SECONDS_FORMAT;
extern int XP_THERMO_SINGULAR_FORMAT;
extern int XP_THERMO_PLURAL_FORMAT;
extern int XP_THERMO_PERCENTAGE_FORMAT;
extern int XP_THERMO_UH;
extern int XP_THERMO_PERCENT_FORM;
extern int XP_THERMO_PERCENT_RATE_FORM;
extern int XP_THERMO_RAW_COUNT_FORM;
extern int XP_THERMO_BYTE_RATE_FORMAT;
extern int XP_THERMO_K_RATE_FORMAT;
extern int XP_THERMO_M_RATE_FORMAT;
extern int XP_THERMO_STALLED_FORMAT;
extern int XP_THERMO_RATE_REMAINING_FORM;
extern int XP_THERMO_RATE_FORM;
PR_END_EXTERN_C
#endif // XP_MAC
#define KILOBYTE (1024L)
#define MINUTE (60L)
#define HOUR (MINUTE * MINUTE)
#define IS_PLURAL(x) (((x) == 1) ? "" : XP_GetString(XP_THERMO_PLURAL_FORMAT)) /* L10N? */
#define ENOUGH_TIME_TO_GUESS 10 /* in seconds */
#define TIME_UNTIL_DETAILS 5
////////////////////////////////////////////////////////////////////////
static void
formatRate(char* buf, PRUint32 len, double bytes_per_sec)
{
if (bytes_per_sec > 0) {
if (bytes_per_sec < KILOBYTE)
PR_snprintf(buf, len, XP_GetString(XP_THERMO_BYTE_RATE_FORMAT),
(PRUint32) bytes_per_sec);
else
PR_snprintf(buf, len, XP_GetString(XP_THERMO_K_RATE_FORMAT),
(bytes_per_sec / ((double) KILOBYTE)));
}
}
static void
formatKnownContentLength(char* buf,
PRUint32 len,
PRUint32 bytesReceived,
PRUint32 contentLength,
PRUint32 elapsed)
{
char rate[32];
*rate = 0;
// the transfer rate
double bytes_per_sec = 0;
if (elapsed > 0)
bytes_per_sec = ((double) bytesReceived) / ((double) elapsed);
formatRate(rate, sizeof(rate), bytes_per_sec);
// format the content length
char length[32];
*length = 0;
if (contentLength < KILOBYTE)
PR_snprintf(length, sizeof(length), XP_GetString(XP_THERMO_BYTE_FORMAT), contentLength);
else
PR_snprintf(length, sizeof(length), XP_GetString(XP_THERMO_KBYTE_FORMAT), contentLength / KILOBYTE);
// the percentage complete
char percent[32];
PRUint32 p = (bytesReceived * 100) / contentLength;
if (p >= 100 && bytesReceived != contentLength)
p = 99;
PR_snprintf(percent, sizeof(percent), XP_GetString(XP_THERMO_PERCENTAGE_FORMAT), p);
// the amount of time remaining
char tleft[32];
*tleft = 0;
if (bytes_per_sec >= KILOBYTE && elapsed >= ENOUGH_TIME_TO_GUESS) {
PRUint32 secs_left =
(PRUint32) (((double) (contentLength - bytesReceived)) / bytes_per_sec);
if (secs_left >= HOUR) {
PR_snprintf(tleft, sizeof(tleft),
XP_GetString(XP_THERMO_HOURS_FORMAT),
secs_left / HOUR,
(secs_left / MINUTE) % MINUTE,
secs_left % MINUTE);
}
else if (secs_left >= MINUTE) {
PR_snprintf(tleft, sizeof(tleft),
XP_GetString(XP_THERMO_MINUTES_FORMAT),
secs_left / MINUTE,
secs_left % MINUTE);
}
else if (secs_left > 0) {
PR_snprintf(tleft, sizeof(tleft),
XP_GetString(XP_THERMO_SECONDS_FORMAT),
secs_left,
IS_PLURAL(secs_left));
}
}
if (*tleft) {
/* "%s of %s (at %s, %s remaining)" */
PR_snprintf(buf, len,
XP_GetString(XP_THERMO_RATE_REMAINING_FORM),
percent, length, rate, tleft);
}
else if (*rate) {
/* "%s of %s (at %s)" */
PR_snprintf(buf, len,
XP_GetString(XP_THERMO_RATE_FORM),
percent, length, rate);
}
else {
/* "%s of %s" */
PR_snprintf(buf, len,
XP_GetString(XP_THERMO_PERCENT_FORM),
percent, length);
}
}
static void
formatUnknownContentLength(char* buf, PRUint32 len, PRUint32 bytesReceived, PRUint32 elapsed)
{
char rate[32];
*rate = 0;
// the transfer rate
double bytes_per_sec = 0;
if (elapsed > 0)
bytes_per_sec = ((double) bytesReceived) / ((double) elapsed);
formatRate(rate, sizeof(rate), bytes_per_sec);
// the number of bytes received
char bytes_received[32];
if (bytesReceived < KILOBYTE)
PR_snprintf(bytes_received, sizeof(bytes_received),
XP_GetString(XP_THERMO_UH),
bytesReceived, IS_PLURAL(bytesReceived));
else
PR_snprintf(bytes_received, sizeof(bytes_received),
XP_GetString(XP_THERMO_KBYTE_FORMAT),
bytesReceived / KILOBYTE);
if (*rate) {
/* "%s read (at %s)" */
PR_snprintf(buf, len, XP_GetString(XP_THERMO_PERCENT_RATE_FORM), bytes_received, rate);
}
else {
PR_snprintf(buf, len, XP_GetString(XP_THERMO_RAW_COUNT_FORM), bytes_received);
}
}
void
nsTopProgressManager::UpdateStatusMessage(AggregateTransferInfo& info)
{
// Compute how much time has elapsed
nsInt64 dt = nsTime(PR_Now()) - fActualStart;
PRUint32 elapsed = dt / nsInt64((PRUint32) PR_USEC_PER_SEC);
char buf[256];
*buf = 0;
if (info.ObjectCount == 1 || info.CompleteCount == 0) {
// If we only have one object that we're transferring, or if
// nothing has completed yet, show the default status message
PL_strncpy(buf, fDefaultStatus, sizeof(buf));
}
if (elapsed > TIME_UNTIL_DETAILS) {
char details[256];
*details = 0;
if (!info.UnknownLengthCount && info.ContentLength > 0) {
formatKnownContentLength(details, sizeof(details),
info.BytesReceived,
info.ContentLength,
elapsed);
}
else if (info.BytesReceived > 0) {
formatUnknownContentLength(details, sizeof(details),
info.BytesReceived,
elapsed);
}
if (*details) {
// XXX needs to go to allxpstr.h
if (*buf)
PL_strcatn(buf, sizeof(buf), ", ");
PL_strcatn(buf, sizeof(buf), details);
}
}
FE_Progress(fContext, buf);
}