1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/service/cloud_print/printer_job_queue_handler.h"
6
7#include <math.h>
8
9#include <algorithm>
10
11#include "base/values.h"
12
13namespace cloud_print {
14
15class TimeProviderImpl : public PrinterJobQueueHandler::TimeProvider {
16 public:
17    virtual base::Time GetNow() OVERRIDE;
18};
19
20base::Time TimeProviderImpl::GetNow() {
21  return base::Time::Now();
22}
23
24JobDetails::JobDetails() {}
25
26JobDetails::~JobDetails() {}
27
28void JobDetails::Clear() {
29  job_id_.clear();
30  job_title_.clear();
31  print_ticket_.clear();
32  print_ticket_mime_type_.clear();
33  print_data_mime_type_.clear();
34  print_data_file_path_ = base::FilePath();
35  print_data_url_.clear();
36  print_ticket_url_.clear();
37  tags_.clear();
38  time_remaining_ = base::TimeDelta();
39}
40
41// static
42bool JobDetails::ordering(const JobDetails& first, const JobDetails& second) {
43  return first.time_remaining_ < second.time_remaining_;
44}
45
46PrinterJobQueueHandler::PrinterJobQueueHandler(TimeProvider* time_provider)
47    : time_provider_(time_provider) {}
48
49PrinterJobQueueHandler::PrinterJobQueueHandler()
50    : time_provider_(new TimeProviderImpl()) {}
51
52PrinterJobQueueHandler::~PrinterJobQueueHandler() {}
53
54void PrinterJobQueueHandler::ConstructJobDetailsFromJson(
55    const base::DictionaryValue* job_data,
56    JobDetails* job_details) {
57  job_details->Clear();
58
59  job_data->GetString(kIdValue, &job_details->job_id_);
60  job_data->GetString(kTitleValue, &job_details->job_title_);
61
62  job_data->GetString(kTicketUrlValue, &job_details->print_ticket_url_);
63  job_data->GetString(kFileUrlValue, &job_details->print_data_url_);
64
65  // Get tags for print job.
66  const base::ListValue* tags = NULL;
67  if (job_data->GetList(kTagsValue, &tags)) {
68    for (size_t i = 0; i < tags->GetSize(); i++) {
69      std::string value;
70      if (tags->GetString(i, &value))
71        job_details->tags_.push_back(value);
72    }
73  }
74}
75
76base::TimeDelta PrinterJobQueueHandler::ComputeBackoffTime(
77    const std::string& job_id) {
78  FailedJobMap::const_iterator job_location = failed_job_map_.find(job_id);
79  if (job_location == failed_job_map_.end()) {
80    return base::TimeDelta();
81  }
82
83  base::TimeDelta backoff_time =
84      base::TimeDelta::FromSeconds(kJobFirstWaitTimeSecs);
85  backoff_time *=
86      // casting argument to double and result to uint64 to avoid compilation
87      // issues
88      static_cast<int64>(pow(
89          static_cast<long double>(kJobWaitTimeExponentialMultiplier),
90          job_location->second.retries_) + 0.5);
91  base::Time scheduled_retry =
92      job_location->second.last_retry_ + backoff_time;
93  base::Time now = time_provider_->GetNow();
94  base::TimeDelta time_remaining;
95
96  if (scheduled_retry < now) {
97    return base::TimeDelta();
98  }
99  return scheduled_retry - now;
100}
101
102void PrinterJobQueueHandler::GetJobsFromQueue(
103    const base::DictionaryValue* json_data,
104    std::vector<JobDetails>* jobs) {
105  std::vector<JobDetails> jobs_with_timeouts;
106
107  jobs->clear();
108
109  const base::ListValue* job_list = NULL;
110  if (!json_data->GetList(kJobListValue, &job_list)) {
111    return;
112  }
113
114  size_t list_size = job_list->GetSize();
115  for (size_t cur_job = 0; cur_job < list_size; cur_job++) {
116    const base::DictionaryValue* job_data = NULL;
117    if (job_list->GetDictionary(cur_job, &job_data)) {
118      JobDetails job_details_current;
119      ConstructJobDetailsFromJson(job_data, &job_details_current);
120
121      job_details_current.time_remaining_ =
122          ComputeBackoffTime(job_details_current.job_id_);
123
124      if (job_details_current.time_remaining_ == base::TimeDelta()) {
125        jobs->push_back(job_details_current);
126      } else {
127        jobs_with_timeouts.push_back(job_details_current);
128      }
129    }
130  }
131
132  sort(jobs_with_timeouts.begin(), jobs_with_timeouts.end(),
133       &JobDetails::ordering);
134  jobs->insert(jobs->end(), jobs_with_timeouts.begin(),
135               jobs_with_timeouts.end());
136}
137
138void PrinterJobQueueHandler::JobDone(const std::string& job_id) {
139  failed_job_map_.erase(job_id);
140}
141
142bool PrinterJobQueueHandler::JobFetchFailed(const std::string& job_id) {
143  FailedJobMetadata metadata;
144  metadata.retries_ = 0;
145  metadata.last_retry_ = time_provider_->GetNow();
146
147  std::pair<FailedJobMap::iterator, bool> job_found =
148      failed_job_map_.insert(FailedJobPair(job_id, metadata));
149
150  // If the job has already failed once, increment the number of retries.
151  // If it has failed too many times, remove it from the map and tell the caller
152  // to report a failure.
153  if (!job_found.second) {
154    if (job_found.first->second.retries_ >= kNumRetriesBeforeAbandonJob) {
155      failed_job_map_.erase(job_found.first);
156      return false;
157    }
158
159    job_found.first->second.retries_ += 1;
160    job_found.first->second.last_retry_ = time_provider_->GetNow();
161  }
162
163  return true;
164}
165
166}  // namespace cloud_print
167
168