print_job_handler.cc revision 3551c9c881056c480085172ff9840cab31610854
1// Copyright 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 "cloud_print/gcp20/prototype/print_job_handler.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/file_util.h"
10#include "base/format_macros.h"
11#include "base/guid.h"
12#include "base/logging.h"
13#include "base/message_loop/message_loop.h"
14#include "base/rand_util.h"
15#include "base/strings/string_util.h"
16#include "base/strings/stringprintf.h"
17#include "base/time/time.h"
18
19namespace {
20
21const int kDraftExpirationSec = 10;
22const int kLocalPrintJobExpirationSec = 20;
23const int kErrorTimeoutSec = 30;
24
25// Errors simulation constants:
26const double kPaperJamProbability = 0.2;
27const int kTooManyDraftsTimeout = 10;
28const int kMaxDrafts = 5;
29
30const base::FilePath::CharType kJobsPath[] = FILE_PATH_LITERAL("printjobs");
31
32bool ValidateTicket(const std::string& ticket) {
33  return true;
34}
35
36std::string GenerateId() {
37  return StringToLowerASCII(base::GenerateGUID());
38}
39
40}  // namespace
41
42struct PrintJobHandler::LocalPrintJobExtended {
43  LocalPrintJobExtended(const LocalPrintJob& job, const std::string& ticket)
44      : data(job),
45        ticket(ticket),
46        state(LocalPrintJob::STATE_DRAFT) {}
47  LocalPrintJobExtended() : state(LocalPrintJob::STATE_DRAFT) {}
48  ~LocalPrintJobExtended() {}
49
50  LocalPrintJob data;
51  std::string ticket;
52  LocalPrintJob::State state;
53  base::Time expiration;
54};
55
56struct PrintJobHandler::LocalPrintJobDraft {
57  LocalPrintJobDraft() {}
58  LocalPrintJobDraft(const std::string& ticket, const base::Time& expiration)
59      : ticket(ticket),
60        expiration(expiration) {}
61  ~LocalPrintJobDraft() {}
62
63  std::string ticket;
64  base::Time expiration;
65};
66
67using base::StringPrintf;
68
69PrintJobHandler::PrintJobHandler() {
70}
71
72PrintJobHandler::~PrintJobHandler() {
73}
74
75LocalPrintJob::CreateResult PrintJobHandler::CreatePrintJob(
76    const std::string& ticket,
77    std::string* job_id_out,
78    // TODO(maksymb): Use base::TimeDelta for timeout values
79    int* expires_in_out,
80    // TODO(maksymb): Use base::TimeDelta for timeout values
81    int* error_timeout_out,
82    std::string* error_description) {
83  if (!ValidateTicket(ticket))
84    return LocalPrintJob::CREATE_INVALID_TICKET;
85
86  // Let's simulate at least some errors just for testing.
87  if (CommandLine::ForCurrentProcess()->HasSwitch("simulate-printing-errors")) {
88    if (base::RandDouble() <= kPaperJamProbability) {
89      *error_description = "Paper jam, try again";
90      return LocalPrintJob::CREATE_PRINTER_ERROR;
91    }
92
93    if (drafts.size() > kMaxDrafts) {  // Another simulation of error: business
94      *error_timeout_out = kTooManyDraftsTimeout;
95      return LocalPrintJob::CREATE_PRINTER_BUSY;
96    }
97  }
98
99  std::string id = GenerateId();
100  drafts[id] = LocalPrintJobDraft(
101      ticket,
102      base::Time::Now() + base::TimeDelta::FromSeconds(kDraftExpirationSec));
103  base::MessageLoop::current()->PostDelayedTask(
104      FROM_HERE,
105      base::Bind(&PrintJobHandler::ForgetDraft, AsWeakPtr(), id),
106      base::TimeDelta::FromSeconds(kDraftExpirationSec));
107
108  *job_id_out = id;
109  *expires_in_out = kDraftExpirationSec;
110  return LocalPrintJob::CREATE_SUCCESS;
111}
112
113LocalPrintJob::SaveResult PrintJobHandler::SaveLocalPrintJob(
114    const LocalPrintJob& job,
115    std::string* job_id_out,
116    int* expires_in_out,
117    std::string* error_description_out,
118    int* timeout_out) {
119  std::string id;
120  int expires_in;
121
122  switch (CreatePrintJob(std::string(), &id, &expires_in,
123                         timeout_out, error_description_out)) {
124    case LocalPrintJob::CREATE_INVALID_TICKET:
125      NOTREACHED();
126      return LocalPrintJob::SAVE_SUCCESS;
127
128    case LocalPrintJob::CREATE_PRINTER_BUSY:
129      return LocalPrintJob::SAVE_PRINTER_BUSY;
130
131    case LocalPrintJob::CREATE_PRINTER_ERROR:
132      return LocalPrintJob::SAVE_PRINTER_ERROR;
133
134    case LocalPrintJob::CREATE_SUCCESS:
135      *job_id_out = id;
136      return CompleteLocalPrintJob(job, id, expires_in_out,
137                                   error_description_out, timeout_out);
138
139    default:
140      NOTREACHED();
141      return LocalPrintJob::SAVE_SUCCESS;
142  }
143}
144
145LocalPrintJob::SaveResult PrintJobHandler::CompleteLocalPrintJob(
146    const LocalPrintJob& job,
147    const std::string& job_id,
148    int* expires_in_out,
149    std::string* error_description_out,
150    int* timeout_out) {
151  if (!drafts.count(job_id)) {
152    *timeout_out = kErrorTimeoutSec;
153    return LocalPrintJob::SAVE_INVALID_PRINT_JOB;
154  }
155
156  std::string suffix = StringPrintf("%s:%s",
157                                    job.user_name.c_str(),
158                                    job.client_name.c_str());
159  std::string file_extension;
160  // TODO(maksymb): Gather together this type checking with Printer
161  // supported types in kCdd.
162  if (job.content_type == "application/pdf") {
163    file_extension = "pdf";
164  } else if (job.content_type == "image/pwg-raster") {
165    file_extension = "pwg";
166  } else if (job.content_type == "image/jpeg") {
167    file_extension = "jpg";
168  } else {
169    error_description_out->clear();
170    return LocalPrintJob::SAVE_INVALID_DOCUMENT_TYPE;
171  }
172  CompleteDraft(job_id, job);
173  std::map<std::string, LocalPrintJobExtended>::iterator current_job =
174      jobs.find(job_id);
175
176  if (!SavePrintJob(current_job->second.data.content,
177                    current_job->second.ticket,
178                    base::Time::Now(),
179                    StringPrintf("%s", job_id.c_str()),
180                    suffix,
181                    current_job->second.data.job_name, file_extension)) {
182    SetJobState(job_id, LocalPrintJob::STATE_ABORTED);
183    *error_description_out = "IO error";
184    return LocalPrintJob::SAVE_PRINTER_ERROR;
185  }
186
187  SetJobState(job_id, LocalPrintJob::STATE_DONE);
188  *expires_in_out = static_cast<int>(GetJobExpiration(job_id).InSeconds());
189  return LocalPrintJob::SAVE_SUCCESS;
190}
191
192bool PrintJobHandler::GetJobState(const std::string& id,
193                                  LocalPrintJob::Info* info_out) {
194  using base::Time;
195
196  std::map<std::string, LocalPrintJobDraft>::iterator draft = drafts.find(id);
197  if (draft != drafts.end()) {
198    info_out->state = LocalPrintJob::STATE_DRAFT;
199    info_out->expires_in =
200        static_cast<int>((draft->second.expiration - Time::Now()).InSeconds());
201    return true;
202  }
203
204  std::map<std::string, LocalPrintJobExtended>::iterator job = jobs.find(id);
205  if (job != jobs.end()) {
206    info_out->state = job->second.state;
207    info_out->expires_in = static_cast<int>(GetJobExpiration(id).InSeconds());
208    return true;
209  }
210  return false;
211}
212
213bool PrintJobHandler::SavePrintJob(const std::string& content,
214                                   const std::string& ticket,
215                                   const base::Time& create_time,
216                                   const std::string& id,
217                                   const std::string& job_name_suffix,
218                                   // suffix is not extension
219                                   // it may be used to mark local printer jobs
220                                   const std::string& title,
221                                   const std::string& file_extension) {
222  VLOG(1) << "Printing printjob: \"" + title + "\"";
223  base::FilePath directory(kJobsPath);
224
225  if (!base::DirectoryExists(directory) &&
226      !file_util::CreateDirectory(directory)) {
227    LOG(WARNING) << "Cannot create directory: " << directory.value();
228    return false;
229  }
230
231  base::Time::Exploded create_time_exploded;
232  create_time.UTCExplode(&create_time_exploded);
233  std::string job_name =
234      StringPrintf("%.4d%.2d%.2d-%.2d:%.2d:%.2d-%.3dms.%s",
235          create_time_exploded.year,
236          create_time_exploded.month,
237          create_time_exploded.day_of_month,
238          create_time_exploded.hour,
239          create_time_exploded.minute,
240          create_time_exploded.second,
241          create_time_exploded.millisecond,
242          id.c_str());
243  if (!job_name_suffix.empty())
244    job_name += "." + job_name_suffix;
245  directory = directory.AppendASCII(job_name);
246
247  if (!base::DirectoryExists(directory) &&
248      !file_util::CreateDirectory(directory)) {
249    LOG(WARNING) << "Cannot create directory: " << directory.value();
250    return false;
251  }
252
253  int written = file_util::WriteFile(directory.AppendASCII("ticket.xml"),
254                                     ticket.data(),
255                                     static_cast<int>(ticket.size()));
256  if (static_cast<size_t>(written) != ticket.size()) {
257    LOG(WARNING) << "Cannot save ticket.";
258    return false;
259  }
260
261  written = file_util::WriteFile(
262      directory.AppendASCII("data." + file_extension),
263      content.data(), static_cast<int>(content.size()));
264  if (static_cast<size_t>(written) != content.size()) {
265    LOG(WARNING) << "Cannot save data.";
266    return false;
267  }
268
269  LOG(INFO) << "Saved printjob: " << job_name << ": " << title;
270  return true;
271}
272
273void PrintJobHandler::SetJobState(const std::string& id,
274                                  LocalPrintJob::State state) {
275  DCHECK(!drafts.count(id)) << "Draft should be completed at first";
276
277  std::map<std::string, LocalPrintJobExtended>::iterator job = jobs.find(id);
278  DCHECK(job != jobs.end());
279  job->second.state = state;
280  switch (state) {
281    case LocalPrintJob::STATE_DRAFT:
282      NOTREACHED();
283      break;
284    case LocalPrintJob::STATE_ABORTED:
285    case LocalPrintJob::STATE_DONE:
286      job->second.expiration =
287          base::Time::Now() +
288          base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec);
289      base::MessageLoop::current()->PostDelayedTask(
290          FROM_HERE,
291          base::Bind(&PrintJobHandler::ForgetLocalJob, AsWeakPtr(), id),
292          base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec));
293      break;
294    default:
295      NOTREACHED();
296  }
297}
298
299void PrintJobHandler::CompleteDraft(const std::string& id,
300                                    const LocalPrintJob& job) {
301  std::map<std::string, LocalPrintJobDraft>::iterator draft = drafts.find(id);
302  if (draft != drafts.end()) {
303    jobs[id] = LocalPrintJobExtended(job, draft->second.ticket);
304    drafts.erase(draft);
305  }
306}
307
308// TODO(maksymb): Use base::Time for expiration
309base::TimeDelta PrintJobHandler::GetJobExpiration(const std::string& id) const {
310  DCHECK(jobs.count(id));
311  base::Time expiration = jobs.at(id).expiration;
312  if (expiration.is_null())
313    return base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec);
314  return expiration - base::Time::Now();
315}
316
317void PrintJobHandler::ForgetDraft(const std::string& id) {
318  drafts.erase(id);
319}
320
321void PrintJobHandler::ForgetLocalJob(const std::string& id) {
322  jobs.erase(id);
323}
324
325