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 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();
                  }
                });
      }
    }
  }