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