blob: 7a4d4cdf1d99a4e065da6f1ad0cba47aceda79b9 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cloud_print/gcp20/prototype/print_job_handler.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "cloud_print/gcp20/prototype/gcp20_switches.h"
namespace {
const int kDraftExpirationSec = 10;
const int kLocalPrintJobExpirationSec = 20;
const int kErrorTimeoutSec = 30;
// Errors simulation constants:
const double kPaperJamProbability = 1.0;
const int kTooManyDraftsTimeout = 10;
const size_t kMaxDrafts = 5;
const base::FilePath::CharType kJobsPath[] = FILE_PATH_LITERAL("printjobs");
bool ValidateTicket(const std::string& ticket) {
return true;
}
std::string GenerateId() {
return base::ToLowerASCII(base::GenerateGUID());
}
} // namespace
struct PrintJobHandler::LocalPrintJobExtended {
LocalPrintJobExtended(const LocalPrintJob& job, const std::string& ticket)
: data(job),
ticket(ticket),
state(LocalPrintJob::STATE_DRAFT) {}
LocalPrintJobExtended() : state(LocalPrintJob::STATE_DRAFT) {}
~LocalPrintJobExtended() {}
LocalPrintJob data;
std::string ticket;
LocalPrintJob::State state;
base::Time expiration;
};
struct PrintJobHandler::LocalPrintJobDraft {
LocalPrintJobDraft() {}
LocalPrintJobDraft(const std::string& ticket, const base::Time& expiration)
: ticket(ticket),
expiration(expiration) {}
~LocalPrintJobDraft() {}
std::string ticket;
base::Time expiration;
};
using base::StringPrintf;
PrintJobHandler::PrintJobHandler() {
}
PrintJobHandler::~PrintJobHandler() {
}
LocalPrintJob::CreateResult PrintJobHandler::CreatePrintJob(
const std::string& ticket,
std::string* job_id_out,
// TODO(maksymb): Use base::TimeDelta for timeout values
int* expires_in_out,
// TODO(maksymb): Use base::TimeDelta for timeout values
int* error_timeout_out,
std::string* error_description) {
if (!ValidateTicket(ticket))
return LocalPrintJob::CREATE_INVALID_TICKET;
// Let's simulate at least some errors just for testing.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSimulatePrintingErrors)) {
if (base::RandDouble() <= kPaperJamProbability) {
*error_description = "Paper jam, try again";
return LocalPrintJob::CREATE_PRINTER_ERROR;
}
if (drafts.size() > kMaxDrafts) { // Another simulation of error: business
*error_timeout_out = kTooManyDraftsTimeout;
return LocalPrintJob::CREATE_PRINTER_BUSY;
}
}
std::string id = GenerateId();
drafts[id] = LocalPrintJobDraft(
ticket,
base::Time::Now() + base::TimeDelta::FromSeconds(kDraftExpirationSec));
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&PrintJobHandler::ForgetDraft, AsWeakPtr(), id),
base::TimeDelta::FromSeconds(kDraftExpirationSec));
*job_id_out = id;
*expires_in_out = kDraftExpirationSec;
return LocalPrintJob::CREATE_SUCCESS;
}
LocalPrintJob::SaveResult PrintJobHandler::SaveLocalPrintJob(
const LocalPrintJob& job,
std::string* job_id_out,
int* expires_in_out,
std::string* error_description_out,
int* timeout_out) {
std::string id;
int expires_in;
switch (CreatePrintJob(std::string(), &id, &expires_in,
timeout_out, error_description_out)) {
case LocalPrintJob::CREATE_INVALID_TICKET:
NOTREACHED();
return LocalPrintJob::SAVE_SUCCESS;
case LocalPrintJob::CREATE_PRINTER_BUSY:
return LocalPrintJob::SAVE_PRINTER_BUSY;
case LocalPrintJob::CREATE_PRINTER_ERROR:
return LocalPrintJob::SAVE_PRINTER_ERROR;
case LocalPrintJob::CREATE_SUCCESS:
*job_id_out = id;
return CompleteLocalPrintJob(job, id, expires_in_out,
error_description_out, timeout_out);
default:
NOTREACHED();
return LocalPrintJob::SAVE_SUCCESS;
}
}
LocalPrintJob::SaveResult PrintJobHandler::CompleteLocalPrintJob(
const LocalPrintJob& job,
const std::string& job_id,
int* expires_in_out,
std::string* error_description_out,
int* timeout_out) {
if (!drafts.count(job_id)) {
*timeout_out = kErrorTimeoutSec;
return LocalPrintJob::SAVE_INVALID_PRINT_JOB;
}
std::string file_extension;
// TODO(maksymb): Gather together this type checking with Printer
// supported types in kCdd.
if (job.content_type == "application/pdf") {
file_extension = "pdf";
} else if (job.content_type == "image/pwg-raster") {
file_extension = "pwg";
} else if (job.content_type == "image/jpeg") {
file_extension = "jpg";
} else {
error_description_out->clear();
return LocalPrintJob::SAVE_INVALID_DOCUMENT_TYPE;
}
CompleteDraft(job_id, job);
std::map<std::string, LocalPrintJobExtended>::iterator current_job =
jobs.find(job_id);
if (!SavePrintJob(current_job->second.data.content,
current_job->second.ticket,
base::Time::Now(),
job_id,
current_job->second.data.job_name, file_extension)) {
SetJobState(job_id, LocalPrintJob::STATE_ABORTED);
*error_description_out = "IO error";
return LocalPrintJob::SAVE_PRINTER_ERROR;
}
SetJobState(job_id, LocalPrintJob::STATE_DONE);
*expires_in_out = static_cast<int>(GetJobExpiration(job_id).InSeconds());
return LocalPrintJob::SAVE_SUCCESS;
}
bool PrintJobHandler::GetJobState(const std::string& id,
LocalPrintJob::Info* info_out) {
using base::Time;
std::map<std::string, LocalPrintJobDraft>::iterator draft = drafts.find(id);
if (draft != drafts.end()) {
info_out->state = LocalPrintJob::STATE_DRAFT;
info_out->expires_in =
static_cast<int>((draft->second.expiration - Time::Now()).InSeconds());
return true;
}
std::map<std::string, LocalPrintJobExtended>::iterator job = jobs.find(id);
if (job != jobs.end()) {
info_out->state = job->second.state;
info_out->expires_in = static_cast<int>(GetJobExpiration(id).InSeconds());
return true;
}
return false;
}
bool PrintJobHandler::SavePrintJob(const std::string& content,
const std::string& ticket,
const base::Time& create_time,
const std::string& id,
const std::string& title,
const std::string& file_extension) {
VLOG(1) << "Printing printjob: \"" + title + "\"";
base::FilePath directory(kJobsPath);
if (!base::DirectoryExists(directory) &&
!base::CreateDirectory(directory)) {
return false;
}
base::Time::Exploded create_time_exploded;
create_time.UTCExplode(&create_time_exploded);
base::FilePath::StringType job_prefix =
StringPrintf(FILE_PATH_LITERAL("%.4d%.2d%.2d-%.2d%.2d%.2d_"),
create_time_exploded.year,
create_time_exploded.month,
create_time_exploded.day_of_month,
create_time_exploded.hour,
create_time_exploded.minute,
create_time_exploded.second);
if (!base::CreateTemporaryDirInDir(directory, job_prefix, &directory)) {
LOG(WARNING) << "Cannot create directory for " << job_prefix;
return false;
}
int written = base::WriteFile(directory.AppendASCII("ticket.xml"),
ticket.data(),
static_cast<int>(ticket.size()));
if (static_cast<size_t>(written) != ticket.size()) {
LOG(WARNING) << "Cannot save ticket.";
return false;
}
written = base::WriteFile(
directory.AppendASCII("data." + file_extension),
content.data(), static_cast<int>(content.size()));
if (static_cast<size_t>(written) != content.size()) {
LOG(WARNING) << "Cannot save data.";
return false;
}
VLOG(0) << "Job saved at " << directory.value();
return true;
}
void PrintJobHandler::SetJobState(const std::string& id,
LocalPrintJob::State state) {
DCHECK(!drafts.count(id)) << "Draft should be completed at first";
std::map<std::string, LocalPrintJobExtended>::iterator job = jobs.find(id);
DCHECK(job != jobs.end());
job->second.state = state;
switch (state) {
case LocalPrintJob::STATE_DRAFT:
NOTREACHED();
break;
case LocalPrintJob::STATE_ABORTED:
case LocalPrintJob::STATE_DONE:
job->second.expiration =
base::Time::Now() +
base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec);
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&PrintJobHandler::ForgetLocalJob, AsWeakPtr(), id),
base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec));
break;
default:
NOTREACHED();
}
}
void PrintJobHandler::CompleteDraft(const std::string& id,
const LocalPrintJob& job) {
std::map<std::string, LocalPrintJobDraft>::iterator draft = drafts.find(id);
if (draft != drafts.end()) {
jobs[id] = LocalPrintJobExtended(job, draft->second.ticket);
drafts.erase(draft);
}
}
// TODO(maksymb): Use base::Time for expiration
base::TimeDelta PrintJobHandler::GetJobExpiration(const std::string& id) const {
DCHECK(jobs.count(id));
base::Time expiration = jobs.at(id).expiration;
if (expiration.is_null())
return base::TimeDelta::FromSeconds(kLocalPrintJobExpirationSec);
return expiration - base::Time::Now();
}
void PrintJobHandler::ForgetDraft(const std::string& id) {
drafts.erase(id);
}
void PrintJobHandler::ForgetLocalJob(const std::string& id) {
jobs.erase(id);
}