protected long getNextScan(Subscription sub) {
    SubscriptionHistory history = sub.getHistory();

    Long fail_count = (Long) sub.getUserData(SCHEDULER_FAILED_SCAN_CONSEC_KEY);

    if (fail_count != null) {

      long fail_time = ((Long) sub.getUserData(SCHEDULER_FAILED_SCAN_TIME_KEY)).longValue();

      long fails = fail_count.longValue();

      long backoff = FAIL_INIT_DELAY;

      for (int i = 1; i < fails; i++) {

        backoff <<= 1;

        if (backoff > FAIL_MAX_DELAY) {

          backoff = FAIL_MAX_DELAY;

          break;
        }
      }

      return (fail_time + backoff);
    }

    return (history.getNextScanTime());
  }
  protected void scanFailed(Subscription sub) {
    sub.setUserData(SCHEDULER_FAILED_SCAN_TIME_KEY, new Long(SystemTime.getCurrentTime()));

    Long fail_count = (Long) sub.getUserData(SCHEDULER_FAILED_SCAN_CONSEC_KEY);

    if (fail_count == null) {

      fail_count = new Long(1);

    } else {

      fail_count = new Long(fail_count.longValue() + 1);
    }

    sub.setUserData(SCHEDULER_FAILED_SCAN_CONSEC_KEY, fail_count);
  }
 protected void scanSuccess(Subscription sub) {
   sub.setUserData(SCHEDULER_FAILED_SCAN_CONSEC_KEY, null);
 }
  protected void schedule() {
    Subscription[] subs = manager.getSubscriptions();

    long now = SystemTime.getCurrentTime();

    for (int i = 0; i < subs.length; i++) {

      Subscription sub = subs[i];

      if (!sub.isSubscribed()) {

        continue;
      }

      SubscriptionHistory history = sub.getHistory();

      if (!history.isEnabled()) {

        continue;
      }

      synchronized (this) {
        Long scan_due = (Long) sub.getUserData(SCHEDULER_NEXT_SCAN_KEY);

        if (scan_due == null) {

          continue;
        }

        long diff = now - scan_due.longValue();

        if (diff < -10 * 1000) {

          continue;
        }

        sub.setUserData(SCHEDULER_NEXT_SCAN_KEY, null);
      }

      long last_scan = history.getLastScanTime();

      try {

        download(sub, true);

      } catch (Throwable e) {

      } finally {

        long new_last_scan = history.getLastScanTime();

        if (new_last_scan == last_scan) {

          scanFailed(sub);

        } else {

          scanSuccess(sub);
        }
      }
    }
  }
  protected void calculateSchedule() {
    Subscription[] subs = manager.getSubscriptions();

    synchronized (this) {
      if (!schedulng_permitted) {

        return;
      }

      if (schedule_in_progress) {

        return;
      }

      long next_ready_time = Long.MAX_VALUE;

      for (int i = 0; i < subs.length; i++) {

        Subscription sub = subs[i];

        if (!sub.isSubscribed()) {

          continue;
        }

        SubscriptionHistory history = sub.getHistory();

        if (!history.isEnabled()) {

          continue;
        }

        long next_scan = getNextScan(sub);

        sub.setUserData(SCHEDULER_NEXT_SCAN_KEY, new Long(next_scan));

        if (next_scan < next_ready_time) {

          next_ready_time = next_scan;
        }
      }

      long old_when = 0;

      if (schedule_event != null) {

        old_when = schedule_event.getWhen();

        schedule_event.cancel();

        schedule_event = null;
      }

      if (next_ready_time < Long.MAX_VALUE) {

        long now = SystemTime.getCurrentTime();

        if (now < last_schedule || now - last_schedule < 30 * 1000) {

          if (next_ready_time - now < 30 * 1000) {

            next_ready_time = now + 30 * 1000;
          }
        }

        log(
            "Calculate : "
                + "old_time="
                + new SimpleDateFormat().format(new Date(old_when))
                + ", new_time="
                + new SimpleDateFormat().format(new Date(next_ready_time)));

        schedule_event =
            SimpleTimer.addEvent(
                "SS:Scheduler",
                next_ready_time,
                new TimerEventPerformer() {
                  public void perform(TimerEvent event) {
                    synchronized (SubscriptionSchedulerImpl.this) {
                      if (schedule_in_progress) {

                        return;
                      }

                      schedule_in_progress = true;

                      last_schedule = SystemTime.getCurrentTime();

                      schedule_event = null;
                    }

                    new AEThread2("SS:Sched", true) {
                      public void run() {
                        try {
                          schedule();

                        } finally {

                          synchronized (SubscriptionSchedulerImpl.this) {
                            schedule_in_progress = false;
                          }

                          calculateSchedule();
                        }
                      }
                    }.start();
                  }
                });
      }
    }
  }
  public void download(final Subscription subs, final SubscriptionResult result) {
    final String dl = result.getDownloadLink();

    if (dl == null) {

      log(
          subs.getName()
              + ": can't download "
              + result.getID()
              + " as no direct download link available");

      return;
    }

    final String key = subs.getID() + ":" + result.getID();

    synchronized (active_result_downloaders) {
      if (active_result_downloaders.contains(key)) {

        return;
      }

      active_result_downloaders.add(key);

      result_downloader.dispatch(
          new AERunnable() {
            public void runSupport() {
              try {
                URL url = new URL(dl);

                ResourceDownloaderFactory rdf = StaticUtilities.getResourceDownloaderFactory();

                ResourceDownloader url_rd = rdf.create(url);

                UrlUtils.setBrowserHeaders(url_rd, subs.getReferer());

                Engine engine = subs.getEngine();

                if (engine instanceof WebEngine) {

                  WebEngine we = (WebEngine) engine;

                  if (we.isNeedsAuth()) {

                    String cookies = we.getCookies();

                    if (cookies != null && cookies.length() > 0) {

                      url_rd.setProperty("URL_Cookie", cookies);
                    }
                  }
                }

                ResourceDownloader mr_rd = rdf.getMetaRefreshDownloader(url_rd);

                InputStream is = mr_rd.download();

                Torrent torrent =
                    new TorrentImpl(TOTorrentFactory.deserialiseFromBEncodedInputStream(is));

                // PlatformTorrentUtils.setContentTitle(torrent, torr );

                Download download =
                    StaticUtilities.getDefaultPluginInterface()
                        .getDownloadManager()
                        .addDownload(torrent);

                if (subs.isPublic()) {

                  subs.addAssociation(torrent.getHash());
                }

                result.setRead(true);

                log(subs.getName() + ": added download " + download.getName());

              } catch (Throwable e) {

                log(subs.getName() + ": Failed to download result " + dl, e);

              } finally {

                active_result_downloaders.remove(key);

                calculateSchedule();
              }
            }
          });
    }
  }