/* -*- 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); }