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 = 1.0; 27const int kTooManyDraftsTimeout = 10; 28const size_t 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 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 188bool 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 209bool 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 = file_util::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 = file_util::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 258void 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 284void 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 294base::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 302void PrintJobHandler::ForgetDraft(const std::string& id) { 303 drafts.erase(id); 304} 305 306void PrintJobHandler::ForgetLocalJob(const std::string& id) { 307 jobs.erase(id); 308} 309