Home | History | Annotate | Download | only in prototype
      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 
     19 namespace {
     20 
     21 const int kDraftExpirationSec = 10;
     22 const int kLocalPrintJobExpirationSec = 20;
     23 const int kErrorTimeoutSec = 30;
     24 
     25 // Errors simulation constants:
     26 const double kPaperJamProbability = 1.0;
     27 const int kTooManyDraftsTimeout = 10;
     28 const size_t kMaxDrafts = 5;
     29 
     30 const base::FilePath::CharType kJobsPath[] = FILE_PATH_LITERAL("printjobs");
     31 
     32 bool ValidateTicket(const std::string& ticket) {
     33   return true;
     34 }
     35 
     36 std::string GenerateId() {
     37   return StringToLowerASCII(base::GenerateGUID());
     38 }
     39 
     40 }  // namespace
     41 
     42 struct 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 
     56 struct 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 
     67 using base::StringPrintf;
     68 
     69 PrintJobHandler::PrintJobHandler() {
     70 }
     71 
     72 PrintJobHandler::~PrintJobHandler() {
     73 }
     74 
     75 LocalPrintJob::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 
    113 LocalPrintJob::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 
    145 LocalPrintJob::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 file_extension;
    157   // TODO(maksymb): Gather together this type checking with Printer
    158   // supported types in kCdd.
    159   if (job.content_type == "application/pdf") {
    160     file_extension = "pdf";
    161   } else if (job.content_type == "image/pwg-raster") {
    162     file_extension = "pwg";
    163   } else if (job.content_type == "image/jpeg") {
    164     file_extension = "jpg";
    165   } else {
    166     error_description_out->clear();
    167     return LocalPrintJob::SAVE_INVALID_DOCUMENT_TYPE;
    168   }
    169   CompleteDraft(job_id, job);
    170   std::map<std::string, LocalPrintJobExtended>::iterator current_job =
    171       jobs.find(job_id);
    172 
    173   if (!SavePrintJob(current_job->second.data.content,
    174                     current_job->second.ticket,
    175                     base::Time::Now(),
    176                     StringPrintf("%s", job_id.c_str()),
    177                     current_job->second.data.job_name, file_extension)) {
    178     SetJobState(job_id, LocalPrintJob::STATE_ABORTED);
    179     *error_description_out = "IO error";
    180     return LocalPrintJob::SAVE_PRINTER_ERROR;
    181   }
    182 
    183   SetJobState(job_id, LocalPrintJob::STATE_DONE);
    184   *expires_in_out = static_cast<int>(GetJobExpiration(job_id).InSeconds());
    185   return LocalPrintJob::SAVE_SUCCESS;
    186 }
    187 
    188 bool PrintJobHandler::GetJobState(const std::string& id,
    189                                   LocalPrintJob::Info* info_out) {
    190   using base::Time;
    191 
    192   std::map<std::string, LocalPrintJobDraft>::iterator draft = drafts.find(id);
    193   if (draft != drafts.end()) {
    194     info_out->state = LocalPrintJob::STATE_DRAFT;
    195     info_out->expires_in =
    196         static_cast<int>((draft->second.expiration - Time::Now()).InSeconds());
    197     return true;
    198   }
    199 
    200   std::map<std::string, LocalPrintJobExtended>::iterator job = jobs.find(id);
    201   if (job != jobs.end()) {
    202     info_out->state = job->second.state;
    203     info_out->expires_in = static_cast<int>(GetJobExpiration(id).InSeconds());
    204     return true;
    205   }
    206   return false;
    207 }
    208 
    209 bool PrintJobHandler::SavePrintJob(const std::string& content,
    210                                    const std::string& ticket,
    211                                    const base::Time& create_time,
    212                                    const std::string& id,
    213                                    const std::string& title,
    214                                    const std::string& file_extension) {
    215   VLOG(1) << "Printing printjob: \"" + title + "\"";
    216   base::FilePath directory(kJobsPath);
    217 
    218   if (!base::DirectoryExists(directory) &&
    219       !base::CreateDirectory(directory)) {
    220     return false;
    221   }
    222 
    223   base::Time::Exploded create_time_exploded;
    224   create_time.UTCExplode(&create_time_exploded);
    225   base::FilePath::StringType job_prefix =
    226       StringPrintf(FILE_PATH_LITERAL("%.4d%.2d%.2d-%.2d%.2d%.2d_"),
    227                    create_time_exploded.year,
    228                    create_time_exploded.month,
    229                    create_time_exploded.day_of_month,
    230                    create_time_exploded.hour,
    231                    create_time_exploded.minute,
    232                    create_time_exploded.second);
    233   if (!base::CreateTemporaryDirInDir(directory, job_prefix, &directory)) {
    234     LOG(WARNING) << "Cannot create directory for " << job_prefix;
    235     return false;
    236   }
    237 
    238   int written = base::WriteFile(directory.AppendASCII("ticket.xml"),
    239                                 ticket.data(),
    240                                 static_cast<int>(ticket.size()));
    241   if (static_cast<size_t>(written) != ticket.size()) {
    242     LOG(WARNING) << "Cannot save ticket.";
    243     return false;
    244   }
    245 
    246   written = base::WriteFile(
    247       directory.AppendASCII("data." + file_extension),
    248       content.data(), static_cast<int>(content.size()));
    249   if (static_cast<size_t>(written) != content.size()) {
    250     LOG(WARNING) << "Cannot save data.";
    251     return false;
    252   }
    253 
    254   VLOG(0) << "Job saved at " << directory.value();
    255   return true;
    256 }
    257 
    258 void PrintJobHandler::SetJobState(const std::string& id,
    259                                   LocalPrintJob::State state) {
    260   DCHECK(!drafts.count(id)) << "Draft should be completed at first";
    261 
    262   std::map<std::string, LocalPrintJobExtended>::iterator job = jobs.find(id);
    263   DCHECK(job != jobs.end());
    264   job->second.state = state;
    265   switch (state) {
    266     case LocalPrintJob::STATE_DRAFT:
    267       NOTREACHED();
    268       break;
    269     case LocalPrintJob::STATE_ABORTED:
    270     case LocalPrintJob::STATE_DONE:
    271       job->second.expiration =
    272           base::Time::Now() +
    273           base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec);
    274       base::MessageLoop::current()->PostDelayedTask(
    275           FROM_HERE,
    276           base::Bind(&PrintJobHandler::ForgetLocalJob, AsWeakPtr(), id),
    277           base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec));
    278       break;
    279     default:
    280       NOTREACHED();
    281   }
    282 }
    283 
    284 void PrintJobHandler::CompleteDraft(const std::string& id,
    285                                     const LocalPrintJob& job) {
    286   std::map<std::string, LocalPrintJobDraft>::iterator draft = drafts.find(id);
    287   if (draft != drafts.end()) {
    288     jobs[id] = LocalPrintJobExtended(job, draft->second.ticket);
    289     drafts.erase(draft);
    290   }
    291 }
    292 
    293 // TODO(maksymb): Use base::Time for expiration
    294 base::TimeDelta PrintJobHandler::GetJobExpiration(const std::string& id) const {
    295   DCHECK(jobs.count(id));
    296   base::Time expiration = jobs.at(id).expiration;
    297   if (expiration.is_null())
    298     return base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec);
    299   return expiration - base::Time::Now();
    300 }
    301 
    302 void PrintJobHandler::ForgetDraft(const std::string& id) {
    303   drafts.erase(id);
    304 }
    305 
    306 void PrintJobHandler::ForgetLocalJob(const std::string& id) {
    307   jobs.erase(id);
    308 }
    309