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