/// <summary>
  /// Gets download progress information for the DownloadRequest by the specified URL.
  /// </summary>
  /// <param name="url"></param>
  /// <returns></returns>
  public DownloadInformation getDownloadInformation(String url) {
    Log.d(LCAT, "getDownloadInformation " + url);
    // Get download information
    DownloadInformation downloadInformation = _completedDownloadCatalog.getDownloadInformation(url);
    if (downloadInformation == null) {
      Log.d(LCAT, "NOT in completed catalog check queue");
      // If no download information is available for a completed download,
      // create it from any matching queued download request
      DownloadRequest request = _downloadQueue.getDownloadRequest(url);
      if (request != null) {
        Log.d(LCAT, "Found in queue");
        downloadInformation = new DownloadInformation();
        downloadInformation.setUrl(request.getUrl());
        downloadInformation.setName(request.getName());
        downloadInformation.setLocale(request.getLocale());
        downloadInformation.setFilePath(request.getFilePath());
        downloadInformation.setLength(request.getLength());
        downloadInformation.setMediaBitsPerSecond(request.getMediaBitsPerSecond());
        downloadInformation.setAvailableLength(request.getAvailableLength());
        downloadInformation.setCreationUtc(request.getCreationUtc());
        downloadInformation.setLastWriteUtc(request.getLastWriteUtc());
        downloadInformation.setLastDownloadBitsPerSecond(request.getLastDownloadBitsPerSecond());
        downloadInformation.setDownloadPriority(request.getDownloadPriority());
        downloadInformation.setIsReadyForPlayback(request.getIsReadyForPlayback());
        downloadInformation.setPermittedNetworkTypes(request.getFinalPermittedNetworkTypes());
        downloadInformation.setStorageLocation(request.getFinalStorageLocation());
      }
    }

    // Return result
    return downloadInformation;
  }
    public void run() {
      Log.d(LCAT, "Download thread started");
      // Locals
      HttpClient webRequest1;
      DownloadRequest downloadRequest;
      DownloadInformation downloadInformation;
      DownloadBatchRequest downloadBatchRequest;
      DownloadBatchInformation downloadBatchInformation;
      DownloadPriority priority;

      // Initialize
      downloadInformation = null;
      downloadBatchInformation = null;
      downloadRequest = this.request;
      downloadBatchRequest =
          downloadRequest.getDownloadBatchRequestId() == null
              ? null
              : _downloadQueue.getDownloadBatchRequest(downloadRequest.getDownloadBatchRequestId());
      priority = downloadRequest.getDownloadPriority();

      try {
        // Initialize download information to report progress
        downloadInformation = new DownloadInformation();
        downloadInformation.setUrl(downloadRequest.getUrl());
        downloadInformation.setName(downloadRequest.getName());
        downloadInformation.setLocale(downloadRequest.getLocale());
        downloadInformation.setFilePath(downloadRequest.getFilePath());
        downloadInformation.setLength(downloadRequest.getLength());
        downloadInformation.setMediaBitsPerSecond(downloadRequest.getMediaBitsPerSecond());
        downloadInformation.setAvailableLength(downloadRequest.getAvailableLength());
        downloadInformation.setCreationUtc(downloadRequest.getCreationUtc());
        downloadInformation.setLastWriteUtc(downloadRequest.getLastWriteUtc());
        downloadInformation.setLastDownloadBitsPerSecond(
            downloadRequest.getLastDownloadBitsPerSecond());
        downloadInformation.setDownloadPriority(downloadRequest.getDownloadPriority());
        downloadInformation.setIsReadyForPlayback(downloadRequest.getIsReadyForPlayback());
        downloadInformation.setPermittedNetworkTypes(
            downloadRequest.getFinalPermittedNetworkTypes());
        downloadInformation.setStorageLocation(downloadRequest.getFinalStorageLocation());

        if (downloadBatchRequest != null) {
          // Get or create batch download information
          synchronized (_downloadBatchInformationLock) {
            downloadBatchInformation =
                _downloadBatchInformationById.get(downloadBatchRequest.getDownloadBatchRequestId());
            if (downloadBatchInformation == null) {
              downloadBatchInformation = new DownloadBatchInformation();
              downloadBatchInformation.setDownloadBatchRequestId(
                  downloadBatchRequest.getDownloadBatchRequestId());
              downloadBatchInformation.setName(downloadBatchRequest.getName());
              downloadBatchInformation.setLocale(downloadBatchRequest.getLocale());
              downloadBatchInformation.setOverrideStorageLocation(
                  downloadBatchRequest.getOverrideStorageLocation());
              downloadBatchInformation.setDownloadPriority(
                  downloadBatchRequest.getDownloadPriority());
              _downloadBatchInformationById.put(
                  downloadBatchRequest.downloadBatchRequestId, downloadBatchInformation);
            }

            // Add this download's information to the batch's collection of download informations
            downloadBatchInformation.getDownloadInformations().add(downloadInformation);
          }
        }

        // Locals
        int byteCount;
        byte[] buffer;
        FileOutputStream fileStream;
        long bpsTrackingSpan;
        Date bpsTrackingStart;
        int trackBpsIterationCount;
        int bytesForBps;
        int fireProgressEventCount;
        boolean downloadBatchRequestIsComplete;

        // Initialize
        fileStream = null;
        fireProgressEventCount = 0;
        trackBpsIterationCount = 0;
        bpsTrackingSpan = 0;
        bytesForBps = 0;

        InputStream responseStream = null;

        try {
          // Open output file and seek to next write position
          String filePath = downloadRequest.getFilePath();
          if (filePath.startsWith("file://")) {
            filePath = filePath.substring(7);
          }

          String message = "resume";
          Log.d(LCAT, "Creating file for writing: " + filePath);
          File file = new File(filePath);
          if (file.exists() == false) {
            message = "start";
            boolean createRet = file.createNewFile();
            Log.d(LCAT, "file.createNewFile returned " + createRet);
          }

          downloadInformation.setMessage(message);
          DownloadStarted.fireEvent(new DownloadEvent(this, downloadInformation, null));

          Log.d(
              LCAT,
              "File length: "
                  + file.length()
                  + " available length: "
                  + downloadRequest.getAvailableLength());
          if (file.length() != downloadRequest.getAvailableLength()) {
            Log.d(LCAT, "File length and available length were different so using file length");
            downloadRequest.setAvailableLength(file.length());
          }

          webRequest1 = new DefaultHttpClient();
          HttpGet request = new HttpGet();
          request.setURI(new URI(downloadRequest.getUrl()));
          if (downloadRequest.getAvailableLength() > OFFSET_ZERO) {
            request.setHeader(
                "Range",
                "bytes="
                    + downloadRequest.getAvailableLength()
                    + '-'
                    + downloadRequest.getLength());
            // Start downloading after already-downloaded bytes
            // webRequest1.AddRange(downloadRequest.getAvailableLength());
          }
          // webRequest1.AutomaticDecompression = DecompressionMethods.GZip |
          // DecompressionMethods.Deflate;
          HttpResponse response = webRequest1.execute(request);
          responseStream = response.getEntity().getContent();
          FileOutputStream outputStream;

          // Get response
          if (response.containsHeader(HTTP_RESPONSE_HEADER_ACCEPT_RANGES) == true
              && response.getFirstHeader(HTTP_RESPONSE_HEADER_ACCEPT_RANGES).getValue()
                  == HTTP_RESPONSE_HEADER_ACCEPT_RANGES_NONE) {
            // Response is for entire file, or server does not support byte ranges, so reset
            // available length to 0
            Log.d(LCAT, "Resetting available length");
            downloadRequest.setAvailableLength(0);
            downloadInformation.setAvailableLength(0);
            outputStream = new FileOutputStream(file, false);
          } else {
            outputStream = new FileOutputStream(file, true);
          }

          // On first download downloadInformation for file, set length of downloadRequest
          if (downloadRequest.getLength() == 0 || downloadRequest.getAvailableLength() == 0) {
            long contentLength =
                Long.parseLong(response.getFirstHeader("content-length").getValue());
            Log.d(LCAT, "Setting length to " + contentLength);
            downloadRequest.setLength(contentLength);
            downloadInformation.setLength(contentLength);
          }

          // FileOutputStream outputStream = new FileOutputStream(file, true);
          outputStream.flush();

          // Receive and store bytes

          long loopCount = 0;
          buffer = new byte[DOWNLOAD_BUFFER_SIZE];
          bpsTrackingStart = new Date();
          while (_downloadRequestUrlsThatMayProceed.containsKey(downloadRequest.getUrl()) == true
              && (byteCount = responseStream.read(buffer, 0, DOWNLOAD_BUFFER_SIZE)) > 0) {
            if (priority == DownloadPriority.Low) {
              Thread.yield();
            } else if (priority == DownloadPriority.Normal && (loopCount % 4) == 0) {
              Thread.yield();
            }
            ++loopCount;

            bytesForBps += byteCount;
            // Update status of downloadRequest
            if (downloadRequest.getDownloadStatus() == DownloadStatus.None) {
              downloadRequest.setDownloadStatus(DownloadStatus.InProgress);
            }

            if (downloadBatchRequest != null
                && downloadBatchRequest.getDownloadStatus() == DownloadStatus.None) {
              downloadBatchRequest.setDownloadStatus(DownloadStatus.InProgress);
            }

            // Store bytes
            outputStream.write(buffer, 0, byteCount);
            outputStream.flush();

            // Update statistics
            downloadRequest.setAvailableLength(downloadRequest.getAvailableLength() + byteCount);
            downloadRequest.setLastWriteUtc(new Date());
            downloadInformation.setLastWriteUtc(downloadRequest.getLastWriteUtc());
            downloadInformation.setAvailableLength(downloadRequest.getAvailableLength());

            // Track bits/second (every n iterations through loop)
            trackBpsIterationCount++;
            if (trackBpsIterationCount == TRACK_BPS_ON_ITERATION_NUMBER) {
              Date now = new Date();

              bpsTrackingSpan = now.getTime() - bpsTrackingStart.getTime();
              if (bpsTrackingSpan != 0) {
                downloadRequest.setLastDownloadBitsPerSecond(
                    (int)
                        ((8.0
                                * // bits per byte
                                bytesForBps)
                            / // bytes downloaded in timespan
                            (bpsTrackingSpan / 1000.0))); // Seconds in timespan
                downloadInformation.setLastDownloadBitsPerSecond(
                    downloadRequest.getLastDownloadBitsPerSecond());
              }
            }

            // Fire progress event (every n iterations through loop)
            fireProgressEventCount++;
            if (fireProgressEventCount == FIRE_PROGRESS_EVENT_ON_ITERATION_NUMBER) {
              fireProgressEventCount = 0;
              DownloadProgress.fireEvent(
                  new DownloadEvent(this, downloadInformation, downloadBatchInformation));
            }

            // Reset tracking of bits/second (every n iterations through loop)
            if (trackBpsIterationCount == TRACK_BPS_ON_ITERATION_NUMBER) {
              trackBpsIterationCount = 0;
              if (bpsTrackingSpan != 0) {
                bytesForBps = 0;
                bpsTrackingStart = new Date();
              }
            }
          }

          // Did the download complete?
          if (_downloadRequestUrlsThatMayProceed.containsKey(downloadRequest.getUrl()) == true) {
            // The download succeeded

            // Close file
            if (outputStream != null) {
              outputStream.close();
              outputStream.flush();
              outputStream = null;
            }

            // Remove download request from queue
            _downloadQueue.remove(downloadRequest.getUrl());
            downloadRequest.setDownloadStatus(
                DownloadStatus
                    .Complete); // This must come after removing the request from the queue to avoid
                                // re-downloading

            // Update batch download status
            if (downloadBatchRequest != null) {
              boolean isInProgress = false;
              for (DownloadRequest dr : downloadBatchRequest.getDownloadRequests()) {
                if (dr.getDownloadStatus() == DownloadStatus.InProgress) {
                  isInProgress = true;
                  break;
                }
              }

              downloadBatchRequest.setDownloadStatus(
                  isInProgress ? DownloadStatus.InProgress : DownloadStatus.None);
            }

            // Close response
            if (responseStream != null) {
              responseStream.close();
              responseStream = null;
            }

            // Remove download tracking
            _downloadRequestUrlsThatMayProceed.remove(downloadRequest.getUrl());

            // Cleanup batch request?
            if (downloadBatchRequest == null) {
              downloadBatchRequestIsComplete = false;
            } else {
              downloadBatchRequestIsComplete =
                  _downloadQueue.downloadBatchRequestIsComplete(
                      downloadBatchRequest.getDownloadBatchRequestId());
              if (downloadBatchRequestIsComplete == true) {
                // Remove batch request from queue
                _downloadQueue.remove(downloadBatchRequest.getDownloadBatchRequestId());

                // Synchronize
                synchronized (_downloadBatchInformationLock) {
                  // Remove batch's download information (if any)
                  if (_downloadBatchInformationById.containsKey(
                          downloadBatchRequest.getDownloadBatchRequestId())
                      == true) {
                    _downloadBatchInformationById.remove(
                        downloadBatchRequest.getDownloadBatchRequestId());
                  }
                }
              }
            }

            // Create record for completed item
            if (downloadRequest.getAvailableLength() >= downloadRequest.getLength()) {
              _completedDownloadCatalog.addCompletedDownload(downloadInformation);
            }

            // Fire downloadInformation completed event
            DownloadCompleted.fireEvent(new DownloadEvent(this, downloadInformation, null));

            // Batch completed?
            if (downloadBatchInformation != null && downloadBatchRequestIsComplete == true) {
              // Create record for completed batch
              _completedDownloadCatalog.addCompletedBatchDownload(downloadBatchInformation);

              // Fire batch completed event?
              DownloadBatchCompleted.fireEvent(
                  new DownloadEvent(this, null, downloadBatchInformation));
            }
          } else {
            // The download was cancelled or paused

            // Reset status of download request in download queue
            if (downloadRequest.getDownloadStatus() == DownloadStatus.InProgress) {
              downloadRequest.setDownloadStatus(DownloadStatus.None);
            }
            if (downloadBatchRequest != null) {
              boolean isInProgress = false;
              for (DownloadRequest dr : downloadBatchRequest.getDownloadRequests()) {
                if (dr.getDownloadStatus() == DownloadStatus.InProgress) {
                  isInProgress = true;
                  break;
                }
              }

              downloadBatchRequest.setDownloadStatus(
                  isInProgress ? DownloadStatus.InProgress : DownloadStatus.None);
            }
          }
        } catch (Exception e) {
          Log.d(LCAT, "Download thread exception " + e.toString());
          // Cancel the entire batch?
          if (downloadBatchRequest != null) {
            cancel(downloadBatchRequest.getDownloadBatchRequestId());
          }

          // Fire download failed events
          DownloadFailed.fireEvent(new DownloadEvent(this, downloadInformation, null));
          if (downloadBatchRequest != null) {
            DownloadBatchFailed.fireEvent(new DownloadEvent(this, null, downloadBatchInformation));
          }
        } finally {
          // Close file if necessary
          if (fileStream != null) {
            fileStream.flush();
            fileStream.close();
            fileStream = null;
          }

          // Close response if necessary
          if (responseStream != null) {
            responseStream.close();
            responseStream = null;
          }
        }
      } catch (Exception e) {
        // Cleanup
        Log.d(LCAT, "Download thread exception " + e.toString());
        downloadRequest.setDownloadStatus(DownloadStatus.None);
        if (downloadBatchRequest != null) {
          boolean isInProgress = false;
          for (DownloadRequest dr : downloadBatchRequest.getDownloadRequests()) {
            if (dr.getDownloadStatus() == DownloadStatus.InProgress) {
              isInProgress = true;
              break;
            }
          }

          downloadBatchRequest.setDownloadStatus(
              isInProgress ? DownloadStatus.InProgress : DownloadStatus.None);
        }

        _downloadRequestUrlsThatMayProceed.remove(downloadRequest.getUrl());

        // Cancel the entire batch?
        if (downloadBatchRequest != null) {
          cancel(downloadBatchRequest.getDownloadBatchRequestId());
        }

        // Fire download failed events
        DownloadFailed.fireEvent(new DownloadEvent(this, downloadInformation, null));
        if (downloadBatchRequest != null) {
          DownloadBatchFailed.fireEvent(new DownloadEvent(this, null, downloadBatchInformation));
        }
      }
    }