toolkit/mozapps/downloads/DownloadUtils.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 this.EXPORTED_SYMBOLS = [ "DownloadUtils" ];
michael@0 7
michael@0 8 /**
michael@0 9 * This module provides the DownloadUtils object which contains useful methods
michael@0 10 * for downloads such as displaying file sizes, transfer times, and download
michael@0 11 * locations.
michael@0 12 *
michael@0 13 * List of methods:
michael@0 14 *
michael@0 15 * [string status, double newLast]
michael@0 16 * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes,
michael@0 17 * [optional] double aSpeed, [optional] double aLastSec)
michael@0 18 *
michael@0 19 * string progress
michael@0 20 * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes)
michael@0 21 *
michael@0 22 * [string timeLeft, double newLast]
michael@0 23 * getTimeLeft(double aSeconds, [optional] double aLastSec)
michael@0 24 *
michael@0 25 * [string dateCompact, string dateComplete]
michael@0 26 * getReadableDates(Date aDate, [optional] Date aNow)
michael@0 27 *
michael@0 28 * [string displayHost, string fullHost]
michael@0 29 * getURIHost(string aURIString)
michael@0 30 *
michael@0 31 * [string convertedBytes, string units]
michael@0 32 * convertByteUnits(int aBytes)
michael@0 33 *
michael@0 34 * [int time, string units, int subTime, string subUnits]
michael@0 35 * convertTimeUnits(double aSecs)
michael@0 36 */
michael@0 37
michael@0 38 const Cc = Components.classes;
michael@0 39 const Ci = Components.interfaces;
michael@0 40 const Cu = Components.utils;
michael@0 41
michael@0 42 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 43
michael@0 44 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
michael@0 45 "resource://gre/modules/PluralForm.jsm");
michael@0 46
michael@0 47 this.__defineGetter__("gDecimalSymbol", function() {
michael@0 48 delete this.gDecimalSymbol;
michael@0 49 return this.gDecimalSymbol = Number(5.4).toLocaleString().match(/\D/);
michael@0 50 });
michael@0 51
michael@0 52 const kDownloadProperties =
michael@0 53 "chrome://mozapps/locale/downloads/downloads.properties";
michael@0 54
michael@0 55 let gStr = {
michael@0 56 statusFormat: "statusFormat3",
michael@0 57 statusFormatInfiniteRate: "statusFormatInfiniteRate",
michael@0 58 statusFormatNoRate: "statusFormatNoRate",
michael@0 59 transferSameUnits: "transferSameUnits2",
michael@0 60 transferDiffUnits: "transferDiffUnits2",
michael@0 61 transferNoTotal: "transferNoTotal2",
michael@0 62 timePair: "timePair2",
michael@0 63 timeLeftSingle: "timeLeftSingle2",
michael@0 64 timeLeftDouble: "timeLeftDouble2",
michael@0 65 timeFewSeconds: "timeFewSeconds",
michael@0 66 timeUnknown: "timeUnknown",
michael@0 67 monthDate: "monthDate2",
michael@0 68 yesterday: "yesterday",
michael@0 69 doneScheme: "doneScheme2",
michael@0 70 doneFileScheme: "doneFileScheme",
michael@0 71 units: ["bytes", "kilobyte", "megabyte", "gigabyte"],
michael@0 72 // Update timeSize in convertTimeUnits if changing the length of this array
michael@0 73 timeUnits: ["seconds", "minutes", "hours", "days"],
michael@0 74 infiniteRate: "infiniteRate",
michael@0 75 };
michael@0 76
michael@0 77 // This lazily initializes the string bundle upon first use.
michael@0 78 this.__defineGetter__("gBundle", function() {
michael@0 79 delete gBundle;
michael@0 80 return this.gBundle = Cc["@mozilla.org/intl/stringbundle;1"].
michael@0 81 getService(Ci.nsIStringBundleService).
michael@0 82 createBundle(kDownloadProperties);
michael@0 83 });
michael@0 84
michael@0 85 // Keep track of at most this many second/lastSec pairs so that multiple calls
michael@0 86 // to getTimeLeft produce the same time left
michael@0 87 const kCachedLastMaxSize = 10;
michael@0 88 let gCachedLast = [];
michael@0 89
michael@0 90 this.DownloadUtils = {
michael@0 91 /**
michael@0 92 * Generate a full status string for a download given its current progress,
michael@0 93 * total size, speed, last time remaining
michael@0 94 *
michael@0 95 * @param aCurrBytes
michael@0 96 * Number of bytes transferred so far
michael@0 97 * @param [optional] aMaxBytes
michael@0 98 * Total number of bytes or -1 for unknown
michael@0 99 * @param [optional] aSpeed
michael@0 100 * Current transfer rate in bytes/sec or -1 for unknown
michael@0 101 * @param [optional] aLastSec
michael@0 102 * Last time remaining in seconds or Infinity for unknown
michael@0 103 * @return A pair: [download status text, new value of "last seconds"]
michael@0 104 */
michael@0 105 getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes,
michael@0 106 aSpeed, aLastSec)
michael@0 107 {
michael@0 108 let [transfer, timeLeft, newLast, normalizedSpeed]
michael@0 109 = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);
michael@0 110
michael@0 111 let [rate, unit] = DownloadUtils.convertByteUnits(normalizedSpeed);
michael@0 112
michael@0 113 let status;
michael@0 114 if (rate === "Infinity") {
michael@0 115 // Infinity download speed doesn't make sense. Show a localized phrase instead.
michael@0 116 let params = [transfer, gBundle.GetStringFromName(gStr.infiniteRate), timeLeft];
michael@0 117 status = gBundle.formatStringFromName(gStr.statusFormatInfiniteRate, params,
michael@0 118 params.length);
michael@0 119 }
michael@0 120 else {
michael@0 121 let params = [transfer, rate, unit, timeLeft];
michael@0 122 status = gBundle.formatStringFromName(gStr.statusFormat, params,
michael@0 123 params.length);
michael@0 124 }
michael@0 125 return [status, newLast];
michael@0 126 },
michael@0 127
michael@0 128 /**
michael@0 129 * Generate a status string for a download given its current progress,
michael@0 130 * total size, speed, last time remaining. The status string contains the
michael@0 131 * time remaining, as well as the total bytes downloaded. Unlike
michael@0 132 * getDownloadStatus, it does not include the rate of download.
michael@0 133 *
michael@0 134 * @param aCurrBytes
michael@0 135 * Number of bytes transferred so far
michael@0 136 * @param [optional] aMaxBytes
michael@0 137 * Total number of bytes or -1 for unknown
michael@0 138 * @param [optional] aSpeed
michael@0 139 * Current transfer rate in bytes/sec or -1 for unknown
michael@0 140 * @param [optional] aLastSec
michael@0 141 * Last time remaining in seconds or Infinity for unknown
michael@0 142 * @return A pair: [download status text, new value of "last seconds"]
michael@0 143 */
michael@0 144 getDownloadStatusNoRate:
michael@0 145 function DU_getDownloadStatusNoRate(aCurrBytes, aMaxBytes, aSpeed,
michael@0 146 aLastSec)
michael@0 147 {
michael@0 148 let [transfer, timeLeft, newLast]
michael@0 149 = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);
michael@0 150
michael@0 151 let params = [transfer, timeLeft];
michael@0 152 let status = gBundle.formatStringFromName(gStr.statusFormatNoRate, params,
michael@0 153 params.length);
michael@0 154 return [status, newLast];
michael@0 155 },
michael@0 156
michael@0 157 /**
michael@0 158 * Helper function that returns a transfer string, a time remaining string,
michael@0 159 * and a new value of "last seconds".
michael@0 160 * @param aCurrBytes
michael@0 161 * Number of bytes transferred so far
michael@0 162 * @param [optional] aMaxBytes
michael@0 163 * Total number of bytes or -1 for unknown
michael@0 164 * @param [optional] aSpeed
michael@0 165 * Current transfer rate in bytes/sec or -1 for unknown
michael@0 166 * @param [optional] aLastSec
michael@0 167 * Last time remaining in seconds or Infinity for unknown
michael@0 168 * @return A triple: [amount transferred string, time remaining string,
michael@0 169 * new value of "last seconds"]
michael@0 170 */
michael@0 171 _deriveTransferRate: function DU__deriveTransferRate(aCurrBytes,
michael@0 172 aMaxBytes, aSpeed,
michael@0 173 aLastSec)
michael@0 174 {
michael@0 175 if (aMaxBytes == null)
michael@0 176 aMaxBytes = -1;
michael@0 177 if (aSpeed == null)
michael@0 178 aSpeed = -1;
michael@0 179 if (aLastSec == null)
michael@0 180 aLastSec = Infinity;
michael@0 181
michael@0 182 // Calculate the time remaining if we have valid values
michael@0 183 let seconds = (aSpeed > 0) && (aMaxBytes > 0) ?
michael@0 184 (aMaxBytes - aCurrBytes) / aSpeed : -1;
michael@0 185
michael@0 186 let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes);
michael@0 187 let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec);
michael@0 188 return [transfer, timeLeft, newLast, aSpeed];
michael@0 189 },
michael@0 190
michael@0 191 /**
michael@0 192 * Generate the transfer progress string to show the current and total byte
michael@0 193 * size. Byte units will be as large as possible and the same units for
michael@0 194 * current and max will be suppressed for the former.
michael@0 195 *
michael@0 196 * @param aCurrBytes
michael@0 197 * Number of bytes transferred so far
michael@0 198 * @param [optional] aMaxBytes
michael@0 199 * Total number of bytes or -1 for unknown
michael@0 200 * @return The transfer progress text
michael@0 201 */
michael@0 202 getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes)
michael@0 203 {
michael@0 204 if (aMaxBytes == null)
michael@0 205 aMaxBytes = -1;
michael@0 206
michael@0 207 let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes);
michael@0 208 let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes);
michael@0 209
michael@0 210 // Figure out which byte progress string to display
michael@0 211 let name, values;
michael@0 212 if (aMaxBytes < 0) {
michael@0 213 name = gStr.transferNoTotal;
michael@0 214 values = [
michael@0 215 progress,
michael@0 216 progressUnits,
michael@0 217 ];
michael@0 218 } else if (progressUnits == totalUnits) {
michael@0 219 name = gStr.transferSameUnits;
michael@0 220 values = [
michael@0 221 progress,
michael@0 222 total,
michael@0 223 totalUnits,
michael@0 224 ];
michael@0 225 } else {
michael@0 226 name = gStr.transferDiffUnits;
michael@0 227 values = [
michael@0 228 progress,
michael@0 229 progressUnits,
michael@0 230 total,
michael@0 231 totalUnits,
michael@0 232 ];
michael@0 233 }
michael@0 234
michael@0 235 return gBundle.formatStringFromName(name, values, values.length);
michael@0 236 },
michael@0 237
michael@0 238 /**
michael@0 239 * Generate a "time left" string given an estimate on the time left and the
michael@0 240 * last time. The extra time is used to give a better estimate on the time to
michael@0 241 * show. Both the time values are doubles instead of integers to help get
michael@0 242 * sub-second accuracy for current and future estimates.
michael@0 243 *
michael@0 244 * @param aSeconds
michael@0 245 * Current estimate on number of seconds left for the download
michael@0 246 * @param [optional] aLastSec
michael@0 247 * Last time remaining in seconds or Infinity for unknown
michael@0 248 * @return A pair: [time left text, new value of "last seconds"]
michael@0 249 */
michael@0 250 getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec)
michael@0 251 {
michael@0 252 if (aLastSec == null)
michael@0 253 aLastSec = Infinity;
michael@0 254
michael@0 255 if (aSeconds < 0)
michael@0 256 return [gBundle.GetStringFromName(gStr.timeUnknown), aLastSec];
michael@0 257
michael@0 258 // Try to find a cached lastSec for the given second
michael@0 259 aLastSec = gCachedLast.reduce(function(aResult, aItem)
michael@0 260 aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec);
michael@0 261
michael@0 262 // Add the current second/lastSec pair unless we have too many
michael@0 263 gCachedLast.push([aSeconds, aLastSec]);
michael@0 264 if (gCachedLast.length > kCachedLastMaxSize)
michael@0 265 gCachedLast.shift();
michael@0 266
michael@0 267 // Apply smoothing only if the new time isn't a huge change -- e.g., if the
michael@0 268 // new time is more than half the previous time; this is useful for
michael@0 269 // downloads that start/resume slowly
michael@0 270 if (aSeconds > aLastSec / 2) {
michael@0 271 // Apply hysteresis to favor downward over upward swings
michael@0 272 // 30% of down and 10% of up (exponential smoothing)
michael@0 273 let (diff = aSeconds - aLastSec) {
michael@0 274 aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff;
michael@0 275 }
michael@0 276
michael@0 277 // If the new time is similar, reuse something close to the last seconds,
michael@0 278 // but subtract a little to provide forward progress
michael@0 279 let diff = aSeconds - aLastSec;
michael@0 280 let diffPct = diff / aLastSec * 100;
michael@0 281 if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5)
michael@0 282 aSeconds = aLastSec - (diff < 0 ? .4 : .2);
michael@0 283 }
michael@0 284
michael@0 285 // Decide what text to show for the time
michael@0 286 let timeLeft;
michael@0 287 if (aSeconds < 4) {
michael@0 288 // Be friendly in the last few seconds
michael@0 289 timeLeft = gBundle.GetStringFromName(gStr.timeFewSeconds);
michael@0 290 } else {
michael@0 291 // Convert the seconds into its two largest units to display
michael@0 292 let [time1, unit1, time2, unit2] =
michael@0 293 DownloadUtils.convertTimeUnits(aSeconds);
michael@0 294
michael@0 295 let pair1 =
michael@0 296 gBundle.formatStringFromName(gStr.timePair, [time1, unit1], 2);
michael@0 297 let pair2 =
michael@0 298 gBundle.formatStringFromName(gStr.timePair, [time2, unit2], 2);
michael@0 299
michael@0 300 // Only show minutes for under 1 hour unless there's a few minutes left;
michael@0 301 // or the second pair is 0.
michael@0 302 if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) {
michael@0 303 timeLeft = gBundle.formatStringFromName(gStr.timeLeftSingle,
michael@0 304 [pair1], 1);
michael@0 305 } else {
michael@0 306 // We've got 2 pairs of times to display
michael@0 307 timeLeft = gBundle.formatStringFromName(gStr.timeLeftDouble,
michael@0 308 [pair1, pair2], 2);
michael@0 309 }
michael@0 310 }
michael@0 311
michael@0 312 return [timeLeft, aSeconds];
michael@0 313 },
michael@0 314
michael@0 315 /**
michael@0 316 * Converts a Date object to two readable formats, one compact, one complete.
michael@0 317 * The compact format is relative to the current date, and is not an accurate
michael@0 318 * representation. For example, only the time is displayed for today. The
michael@0 319 * complete format always includes both the date and the time, excluding the
michael@0 320 * seconds, and is often shown when hovering the cursor over the compact
michael@0 321 * representation.
michael@0 322 *
michael@0 323 * @param aDate
michael@0 324 * Date object representing the date and time to format. It is assumed
michael@0 325 * that this value represents a past date.
michael@0 326 * @param [optional] aNow
michael@0 327 * Date object representing the current date and time. The real date
michael@0 328 * and time of invocation is used if this parameter is omitted.
michael@0 329 * @return A pair: [compact text, complete text]
michael@0 330 */
michael@0 331 getReadableDates: function DU_getReadableDates(aDate, aNow)
michael@0 332 {
michael@0 333 if (!aNow) {
michael@0 334 aNow = new Date();
michael@0 335 }
michael@0 336
michael@0 337 let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"]
michael@0 338 .getService(Ci.nsIScriptableDateFormat);
michael@0 339
michael@0 340 // Figure out when today begins
michael@0 341 let today = new Date(aNow.getFullYear(), aNow.getMonth(), aNow.getDate());
michael@0 342
michael@0 343 // Figure out if the time is from today, yesterday, this week, etc.
michael@0 344 let dateTimeCompact;
michael@0 345 if (aDate >= today) {
michael@0 346 // After today started, show the time
michael@0 347 dateTimeCompact = dts.FormatTime("",
michael@0 348 dts.timeFormatNoSeconds,
michael@0 349 aDate.getHours(),
michael@0 350 aDate.getMinutes(),
michael@0 351 0);
michael@0 352 } else if (today - aDate < (24 * 60 * 60 * 1000)) {
michael@0 353 // After yesterday started, show yesterday
michael@0 354 dateTimeCompact = gBundle.GetStringFromName(gStr.yesterday);
michael@0 355 } else if (today - aDate < (6 * 24 * 60 * 60 * 1000)) {
michael@0 356 // After last week started, show day of week
michael@0 357 dateTimeCompact = aDate.toLocaleFormat("%A");
michael@0 358 } else {
michael@0 359 // Show month/day
michael@0 360 let month = aDate.toLocaleFormat("%B");
michael@0 361 // Remove leading 0 by converting the date string to a number
michael@0 362 let date = Number(aDate.toLocaleFormat("%d"));
michael@0 363 dateTimeCompact = gBundle.formatStringFromName(gStr.monthDate, [month, date], 2);
michael@0 364 }
michael@0 365
michael@0 366 let dateTimeFull = dts.FormatDateTime("",
michael@0 367 dts.dateFormatLong,
michael@0 368 dts.timeFormatNoSeconds,
michael@0 369 aDate.getFullYear(),
michael@0 370 aDate.getMonth() + 1,
michael@0 371 aDate.getDate(),
michael@0 372 aDate.getHours(),
michael@0 373 aDate.getMinutes(),
michael@0 374 0);
michael@0 375
michael@0 376 return [dateTimeCompact, dateTimeFull];
michael@0 377 },
michael@0 378
michael@0 379 /**
michael@0 380 * Get the appropriate display host string for a URI string depending on if
michael@0 381 * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
michael@0 382 *
michael@0 383 * @param aURIString
michael@0 384 * The URI string to try getting an eTLD + 1, etc.
michael@0 385 * @return A pair: [display host for the URI string, full host name]
michael@0 386 */
michael@0 387 getURIHost: function DU_getURIHost(aURIString)
michael@0 388 {
michael@0 389 let ioService = Cc["@mozilla.org/network/io-service;1"].
michael@0 390 getService(Ci.nsIIOService);
michael@0 391 let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
michael@0 392 getService(Ci.nsIEffectiveTLDService);
michael@0 393 let idnService = Cc["@mozilla.org/network/idn-service;1"].
michael@0 394 getService(Ci.nsIIDNService);
michael@0 395
michael@0 396 // Get a URI that knows about its components
michael@0 397 let uri = ioService.newURI(aURIString, null, null);
michael@0 398
michael@0 399 // Get the inner-most uri for schemes like jar:
michael@0 400 if (uri instanceof Ci.nsINestedURI)
michael@0 401 uri = uri.innermostURI;
michael@0 402
michael@0 403 let fullHost;
michael@0 404 try {
michael@0 405 // Get the full host name; some special URIs fail (data: jar:)
michael@0 406 fullHost = uri.host;
michael@0 407 } catch (e) {
michael@0 408 fullHost = "";
michael@0 409 }
michael@0 410
michael@0 411 let displayHost;
michael@0 412 try {
michael@0 413 // This might fail if it's an IP address or doesn't have more than 1 part
michael@0 414 let baseDomain = eTLDService.getBaseDomain(uri);
michael@0 415
michael@0 416 // Convert base domain for display; ignore the isAscii out param
michael@0 417 displayHost = idnService.convertToDisplayIDN(baseDomain, {});
michael@0 418 } catch (e) {
michael@0 419 // Default to the host name
michael@0 420 displayHost = fullHost;
michael@0 421 }
michael@0 422
michael@0 423 // Check if we need to show something else for the host
michael@0 424 if (uri.scheme == "file") {
michael@0 425 // Display special text for file protocol
michael@0 426 displayHost = gBundle.GetStringFromName(gStr.doneFileScheme);
michael@0 427 fullHost = displayHost;
michael@0 428 } else if (displayHost.length == 0) {
michael@0 429 // Got nothing; show the scheme (data: about: moz-icon:)
michael@0 430 displayHost =
michael@0 431 gBundle.formatStringFromName(gStr.doneScheme, [uri.scheme], 1);
michael@0 432 fullHost = displayHost;
michael@0 433 } else if (uri.port != -1) {
michael@0 434 // Tack on the port if it's not the default port
michael@0 435 let port = ":" + uri.port;
michael@0 436 displayHost += port;
michael@0 437 fullHost += port;
michael@0 438 }
michael@0 439
michael@0 440 return [displayHost, fullHost];
michael@0 441 },
michael@0 442
michael@0 443 /**
michael@0 444 * Converts a number of bytes to the appropriate unit that results in an
michael@0 445 * internationalized number that needs fewer than 4 digits.
michael@0 446 *
michael@0 447 * @param aBytes
michael@0 448 * Number of bytes to convert
michael@0 449 * @return A pair: [new value with 3 sig. figs., its unit]
michael@0 450 */
michael@0 451 convertByteUnits: function DU_convertByteUnits(aBytes)
michael@0 452 {
michael@0 453 let unitIndex = 0;
michael@0 454
michael@0 455 // Convert to next unit if it needs 4 digits (after rounding), but only if
michael@0 456 // we know the name of the next unit
michael@0 457 while ((aBytes >= 999.5) && (unitIndex < gStr.units.length - 1)) {
michael@0 458 aBytes /= 1024;
michael@0 459 unitIndex++;
michael@0 460 }
michael@0 461
michael@0 462 // Get rid of insignificant bits by truncating to 1 or 0 decimal points
michael@0 463 // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
michael@0 464 // added in bug 462064: (unitIndex != 0) makes sure that no decimal digit for bytes appears when aBytes < 100
michael@0 465 aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) && (unitIndex != 0) ? 1 : 0);
michael@0 466
michael@0 467 if (gDecimalSymbol != ".")
michael@0 468 aBytes = aBytes.replace(".", gDecimalSymbol);
michael@0 469 return [aBytes, gBundle.GetStringFromName(gStr.units[unitIndex])];
michael@0 470 },
michael@0 471
michael@0 472 /**
michael@0 473 * Converts a number of seconds to the two largest units. Time values are
michael@0 474 * whole numbers, and units have the correct plural/singular form.
michael@0 475 *
michael@0 476 * @param aSecs
michael@0 477 * Seconds to convert into the appropriate 2 units
michael@0 478 * @return 4-item array [first value, its unit, second value, its unit]
michael@0 479 */
michael@0 480 convertTimeUnits: function DU_convertTimeUnits(aSecs)
michael@0 481 {
michael@0 482 // These are the maximum values for seconds, minutes, hours corresponding
michael@0 483 // with gStr.timeUnits without the last item
michael@0 484 let timeSize = [60, 60, 24];
michael@0 485
michael@0 486 let time = aSecs;
michael@0 487 let scale = 1;
michael@0 488 let unitIndex = 0;
michael@0 489
michael@0 490 // Keep converting to the next unit while we have units left and the
michael@0 491 // current one isn't the largest unit possible
michael@0 492 while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) {
michael@0 493 time /= timeSize[unitIndex];
michael@0 494 scale *= timeSize[unitIndex];
michael@0 495 unitIndex++;
michael@0 496 }
michael@0 497
michael@0 498 let value = convertTimeUnitsValue(time);
michael@0 499 let units = convertTimeUnitsUnits(value, unitIndex);
michael@0 500
michael@0 501 let extra = aSecs - value * scale;
michael@0 502 let nextIndex = unitIndex - 1;
michael@0 503
michael@0 504 // Convert the extra time to the next largest unit
michael@0 505 for (let index = 0; index < nextIndex; index++)
michael@0 506 extra /= timeSize[index];
michael@0 507
michael@0 508 let value2 = convertTimeUnitsValue(extra);
michael@0 509 let units2 = convertTimeUnitsUnits(value2, nextIndex);
michael@0 510
michael@0 511 return [value, units, value2, units2];
michael@0 512 },
michael@0 513 };
michael@0 514
michael@0 515 /**
michael@0 516 * Private helper for convertTimeUnits that gets the display value of a time
michael@0 517 *
michael@0 518 * @param aTime
michael@0 519 * Time value for display
michael@0 520 * @return An integer value for the time rounded down
michael@0 521 */
michael@0 522 function convertTimeUnitsValue(aTime)
michael@0 523 {
michael@0 524 return Math.floor(aTime);
michael@0 525 }
michael@0 526
michael@0 527 /**
michael@0 528 * Private helper for convertTimeUnits that gets the display units of a time
michael@0 529 *
michael@0 530 * @param aTime
michael@0 531 * Time value for display
michael@0 532 * @param aIndex
michael@0 533 * Index into gStr.timeUnits for the appropriate unit
michael@0 534 * @return The appropriate plural form of the unit for the time
michael@0 535 */
michael@0 536 function convertTimeUnitsUnits(aTime, aIndex)
michael@0 537 {
michael@0 538 // Negative index would be an invalid unit, so just give empty
michael@0 539 if (aIndex < 0)
michael@0 540 return "";
michael@0 541
michael@0 542 return PluralForm.get(aTime, gBundle.GetStringFromName(gStr.timeUnits[aIndex]));
michael@0 543 }
michael@0 544
michael@0 545 /**
michael@0 546 * Private helper function to log errors to the error console and command line
michael@0 547 *
michael@0 548 * @param aMsg
michael@0 549 * Error message to log or an array of strings to concat
michael@0 550 */
michael@0 551 function log(aMsg)
michael@0 552 {
michael@0 553 let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
michael@0 554 Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
michael@0 555 logStringMessage(msg);
michael@0 556 dump(msg + "\n");
michael@0 557 }

mercurial