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