/** @return whether we killed any hopeless update downloads */
  private void killHopelessUpdates(List<? extends DownloadInformation> updates) {
    if (updates == null) return;

    if (!downloadManager.get().hasInNetworkDownload()) return;

    long now = clock.now();
    for (DownloadInformation info : updates) {
      Downloader downloader = downloadManager.get().getDownloaderForURN(info.getUpdateURN());
      if (downloader != null && downloader instanceof InNetworkDownloader) {
        InNetworkDownloader iDownloader = (InNetworkDownloader) downloader;
        if (isHopeless(iDownloader, now)) iDownloader.stop(false);
      }
    }
  }
  /**
   * Tries to download updates.
   *
   * @return whether we had any non-hopeless updates.
   */
  private void downloadUpdates(
      List<? extends DownloadInformation> toDownload, ReplyHandler source) {
    if (toDownload == null) toDownload = Collections.emptyList();

    killObsoleteUpdates(toDownload);

    for (DownloadInformation next : toDownload) {
      if (isHopeless(next)) continue;

      if (downloadManager.get().isSavedDownloadsLoaded()
          && fileManager.get().getManagedFileList().isLoadFinished()) {

        // TODO: remove the cast
        ManagedDownloader md =
            (ManagedDownloader) downloadManager.get().getDownloaderForURN(next.getUpdateURN());

        // Skip to the next one since we already have a complete file.
        if (hasCompleteFile(next.getUpdateURN())) {
          if (md != null) {
            md.stop(false);
          }
          continue;
        }

        // If we don't have an existing download ...
        // and there's no existing InNetwork downloads &
        // no existing Store downloads &
        // we're allowed to start a new one.
        if (md == null && !downloadManager.get().hasInNetworkDownload() && canStartDownload()) {
          LOG.debug("Starting a new InNetwork Download");
          try {
            md = (ManagedDownloader) downloadManager.get().download(next, clock.now());
          } catch (SaveLocationException sle) {
            LOG.error("Unable to construct download", sle);
          }
        }

        if (md != null) {
          if (source != null) md.addDownload(rfd(source, next), false);
          else addCurrentDownloadSources(md, next);
        }
      }
    }
  }
 /** Constructs an RFD out of the given information & connection. */
 private RemoteFileDesc rfd(ReplyHandler rh, DownloadInformation info) {
   Set<URN> urns = new UrnSet(info.getUpdateURN());
   return remoteFileDescFactory.createRemoteFileDesc(
       new ConnectableImpl(
           rh.getInetSocketAddress(),
           rh instanceof Connectable ? ((Connectable) rh).isTLSCapable() : false),
       Integer.MAX_VALUE,
       info.getUpdateFileName(),
       info.getSize(),
       rh.getClientGUID(),
       0,
       false,
       2,
       false,
       null,
       urns,
       false,
       "LIME",
       -1);
 }
  /**
   * kills all in-network downloaders whose URNs are not listed in the list of updates. Deletes any
   * files in the folder that are not listed in the update message.
   */
  private void killObsoleteUpdates(List<? extends DownloadInformation> toDownload) {
    if (!downloadManager.get().isSavedDownloadsLoaded()
        || !fileManager.get().getManagedFileList().isLoadFinished()) return;

    if (_killingObsoleteNecessary) {
      _killingObsoleteNecessary = false;
      downloadManager.get().killDownloadersNotListed(toDownload);

      Set<URN> urns = new HashSet<URN>(toDownload.size());
      for (DownloadInformation data : toDownload) urns.add(data.getUpdateURN());

      List<FileDesc> shared =
          fileManager
              .get()
              .getGnutellaFileList()
              .getFilesInDirectory(LibraryUtils.PREFERENCE_SHARE);
      for (FileDesc fd : shared) {
        if (fd.getSHA1Urn() != null && !urns.contains(fd.getSHA1Urn())) {
          fileManager.get().getManagedFileList().remove(fd.getFile());
          fd.getFile().delete();
        }
      }
    }
  }
  /// <summary>
  /// Restarts download of the in-progress or completed item with the specified URL, beginning at
  // the specified byte offset.
  /// </summary>
  /// <param name="url"></param>
  /// <param name="byteOffset"></param>
  public void restart(String url, long byteOffset) {
    // Locals
    DownloadRequest downloadRequest;
    DownloadInformation downloadInformation;

    // Get download information
    downloadRequest = _downloadQueue.getDownloadRequest(url);
    if (downloadRequest == null) {
      downloadInformation = _completedDownloadCatalog.getDownloadInformation(url);
      if (downloadInformation == null) {
        return;
      }

      downloadRequest = new DownloadRequest();
      downloadRequest.setUrl(downloadInformation.getUrl());
      downloadRequest.setName(downloadInformation.getName());
      downloadRequest.setLocale(downloadInformation.getLocale());
      downloadRequest.setOverrideStorageLocation(downloadInformation.getStorageLocation());
      downloadRequest.setDownloadPriority(downloadInformation.getDownloadPriority());
      downloadRequest.setOverridePermittedNetworkTypes(
          downloadInformation.getPermittedNetworkTypes());

      if (byteOffset == OFFSET_ZERO) {
        delete(downloadInformation.getUrl());
      } else {
        downloadRequest.setAvailableLength(byteOffset);
      }
    } else {
      downloadRequest = new DownloadRequest();
      downloadRequest.setUrl(downloadRequest.getUrl());
      downloadRequest.setName(downloadRequest.getName());
      downloadRequest.setLocale(downloadRequest.getLocale());
      downloadRequest.setOverrideStorageLocation(downloadRequest.getOverrideStorageLocation());
      downloadRequest.setDownloadPriority(downloadRequest.getDownloadPriority());
      downloadRequest.setOverridePermittedNetworkTypes(
          downloadRequest.getOverridePermittedNetworkTypes());
    }

    // Restart download information
    _downloadQueue.add(downloadRequest, _defaultStorageLocation, _permittedNetworkTypes);
  }
    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));
        }
      }
    }
  /// <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;
  }
 /** @return if the given update is considered hopeless */
 private static boolean isHopeless(DownloadInformation info) {
   return UpdateSettings.FAILED_UPDATES.contains(info.getUpdateURN().httpStringValue());
 }