/*
 * Copyright 2001-2009, Haiku.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		I.R. Adema
 *		Stefano Ceccherini (burton666@libero.it)
 *		Michael Pfeiffer
 *		julun <host.haiku@gmx.de>
 */
 
 
#include <PrintJob.h>
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <Alert.h>
#include <Application.h>
#include <Button.h>
#include <Debug.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Messenger.h>
#include <NodeInfo.h>
#include <OS.h>
#include <Path.h>
#include <Region.h>
#include <Roster.h>
#include <SystemCatalog.h>
#include <View.h>
 
#include <AutoDeleter.h>
#include <pr_server.h>
#include <ViewPrivate.h>
 
using BPrivate::gSystemCatalog;
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PrintJob"
 
#undef B_TRANSLATE
#define B_TRANSLATE(str) \
	gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "PrintJob")
 
 
/*!	Summary of spool file:
 
		|-----------------------------------|
		|         print_file_header         |
		|-----------------------------------|
		|    BMessage print_job_settings    |
		|-----------------------------------|
		|                                   |
		| ********** (first page) ********* |
		| *                               * |
		| *         _page_header_         * |
		| * ----------------------------- * |
		| * |---------------------------| * |
		| * |       BPoint where        | * |
		| * |       BRect bounds        | * |
		| * |       BPicture pic        | * |
		| * |---------------------------| * |
		| * |---------------------------| * |
		| * |       BPoint where        | * |
		| * |       BRect bounds        | * |
		| * |       BPicture pic        | * |
		| * |---------------------------| * |
		| ********************************* |
		|                                   |
		| ********* (second page) ********* |
		| *                               * |
		| *         _page_header_         * |
		| * ----------------------------- * |
		| * |---------------------------| * |
		| * |       BPoint where        | * |
		| * |       BRect bounds        | * |
		| * |       BPicture pic        | * |
		| * |---------------------------| * |
		| ********************************* |
		|-----------------------------------|
 
	BeOS R5 print_file_header.version is 1 << 16
	BeOS R5 print_file_header.first_page is -1
 
	each page can consist of a collection of picture structures
	remaining pages start at _page_header_.next_page of previous _page_header_
 
	See also: "How to Write a BeOS R5 Printer Driver" for description of spool
	file format: http://haiku-os.org/documents/dev/how_to_write_a_printer_driver
*/
 
 
struct _page_header_ {
	int32 number_of_pictures;
	off_t next_page;
	int32 reserved[10];
} _PACKED;
 
 
static void
ShowError(const char* message)
{
	BAlert* alert = new BAlert(B_TRANSLATE("Error"), message, B_TRANSLATE("OK"));
	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
	alert->Go();
}
 
 
// #pragma mark -- PrintServerMessenger
 
 
namespace BPrivate {
 
 
class PrintServerMessenger {
public:
							PrintServerMessenger(uint32 what, BMessage* input);
							~PrintServerMessenger();
 
			BMessage*		Request();
			status_t		SendRequest();
 
			void			SetResult(BMessage* result);
			BMessage*		Result() const { return fResult; }
 
	static	status_t		GetPrintServerMessenger(BMessenger& messenger);
 
private:
			void			RejectUserInput();
			void			AllowUserInput();
			void			DeleteSemaphore();
	static	status_t		MessengerThread(void* data);
 
			uint32			fWhat;
			BMessage*		fInput;
			BMessage*		fRequest;
			BMessage*		fResult;
			sem_id			fThreadCompleted;
			BAlert*			fHiddenApplicationModalWindow;
};
 
 
}	// namespace BPrivate
 
 
using namespace BPrivate;
 
 
// #pragma mark -- BPrintJob
 
 
BPrintJob::BPrintJob(const char* jobName)
	:
	fPrintJobName(NULL),
	fSpoolFile(NULL),
	fError(B_NO_INIT),
	fSetupMessage(NULL),
	fDefaultSetupMessage(NULL),
	fAbort(0),
	fCurrentPageHeader(NULL)
{
	memset(&fSpoolFileHeader, 0, sizeof(print_file_header));
 
	if (jobName != NULL && jobName[0])
		fPrintJobName = strdup(jobName);
 
	fCurrentPageHeader = new _page_header_;
	if (fCurrentPageHeader != NULL)
		memset(fCurrentPageHeader, 0, sizeof(_page_header_));
}
 
 
BPrintJob::~BPrintJob()
{
	CancelJob();
 
	free(fPrintJobName);
	delete fSetupMessage;
	delete fDefaultSetupMessage;
	delete fCurrentPageHeader;
}
 
 
status_t
BPrintJob::ConfigPage()
{
	PrintServerMessenger messenger(PSRV_SHOW_PAGE_SETUP, fSetupMessage);
	status_t status = messenger.SendRequest();
	if (status != B_OK)
		return status;
 
	delete fSetupMessage;
	fSetupMessage = messenger.Result();
	_HandlePageSetup(fSetupMessage);
 
	return B_OK;
}
 
 
status_t
BPrintJob::ConfigJob()
{
	PrintServerMessenger messenger(PSRV_SHOW_PRINT_SETUP, fSetupMessage);
	status_t status = messenger.SendRequest();
	if (status != B_OK)
		return status;
 
	delete fSetupMessage;
	fSetupMessage = messenger.Result();
	if (!_HandlePrintSetup(fSetupMessage))
		return B_ERROR;
 
	fError = B_OK;
	return B_OK;
}
 
 
void
BPrintJob::BeginJob()
{
	fError = B_ERROR;
 
	// can not start a new job until it has been commited or cancelled
	if (fSpoolFile != NULL || fCurrentPageHeader == NULL)
		return;
 
	// TODO show alert, setup message is required
	if (fSetupMessage == NULL)
		return;
 
	// create spool file
	BPath path;
	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path);
	if (status != B_OK)
		return;
 
	char *printer = _GetCurrentPrinterName();
	if (printer == NULL)
		return;
	MemoryDeleter _(printer);
 
	path.Append(printer);
 
	char mangledName[B_FILE_NAME_LENGTH];
	_GetMangledName(mangledName, B_FILE_NAME_LENGTH);
 
	path.Append(mangledName);
	if (path.InitCheck() != B_OK)
		return;
 
	// TODO: fSpoolFileName should store the name only (not path which can be
	// 1024 bytes long)
	strlcpy(fSpoolFileName, path.Path(), sizeof(fSpoolFileName));
	fSpoolFile = new BFile(fSpoolFileName, B_READ_WRITE | B_CREATE_FILE);
 
	if (fSpoolFile->InitCheck() != B_OK) {
		CancelJob();
		return;
	}
 
	// add print_file_header
	// page_count is updated in CommitJob()
	// on BeOS R5 the offset to the first page was always -1
	fSpoolFileHeader.version = 1 << 16;
	fSpoolFileHeader.page_count = 0;
	fSpoolFileHeader.first_page = (off_t)-1;
 
	if (fSpoolFile->Write(&fSpoolFileHeader, sizeof(print_file_header))
			!= sizeof(print_file_header)) {
		CancelJob();
		return;
	}
 
	// add printer settings message
	if (!fSetupMessage->HasString(PSRV_FIELD_CURRENT_PRINTER))
		fSetupMessage->AddString(PSRV_FIELD_CURRENT_PRINTER, printer);
 
	_AddSetupSpec();
	_NewPage();
 
	// state variables
	fAbort = 0;
	fError = B_OK;
}
 
 
void
BPrintJob::CommitJob()
{
	if (fSpoolFile == NULL)
		return;
 
	if (fSpoolFileHeader.page_count == 0) {
		ShowError(B_TRANSLATE("No pages to print!"));
		CancelJob();
		return;
	}
 
	// update spool file
	_EndLastPage();
 
	// write spool file header
	fSpoolFile->Seek(0, SEEK_SET);
	fSpoolFile->Write(&fSpoolFileHeader, sizeof(print_file_header));
 
	// set file attributes
	app_info appInfo;
	be_app->GetAppInfo(&appInfo);
	const char* printerName = "";
	fSetupMessage->FindString(PSRV_FIELD_CURRENT_PRINTER, &printerName);
 
	BNodeInfo info(fSpoolFile);
	info.SetType(PSRV_SPOOL_FILETYPE);
 
	fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_PAGECOUNT, B_INT32_TYPE, 0,
		&fSpoolFileHeader.page_count, sizeof(int32));
	fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_DESCRIPTION, B_STRING_TYPE, 0,
		fPrintJobName, strlen(fPrintJobName) + 1);
	fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_PRINTER, B_STRING_TYPE, 0,
		printerName, strlen(printerName) + 1);
	fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_STATUS, B_STRING_TYPE, 0,
		PSRV_JOB_STATUS_WAITING, strlen(PSRV_JOB_STATUS_WAITING) + 1);
	fSpoolFile->WriteAttr(PSRV_SPOOL_ATTR_MIMETYPE, B_STRING_TYPE, 0,
		appInfo.signature, strlen(appInfo.signature) + 1);
 
	delete fSpoolFile;
	fSpoolFile = NULL;
	fError = B_ERROR;
 
	// notify print server
	BMessenger printServer;
	if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
		return;
 
	BMessage request(PSRV_PRINT_SPOOLED_JOB);
	request.AddString("JobName", fPrintJobName);
	request.AddString("Spool File", fSpoolFileName);
 
	BMessage reply;
	printServer.SendMessage(&request, &reply);
}
 
 
void
BPrintJob::CancelJob()
{
	if (fSpoolFile == NULL)
		return;
 
	fAbort = 1;
	BEntry(fSpoolFileName).Remove();
	delete fSpoolFile;
	fSpoolFile = NULL;
}
 
 
void
BPrintJob::SpoolPage()
{
	if (fSpoolFile == NULL)
		return;
 
	if (fCurrentPageHeader->number_of_pictures == 0)
		return;
 
	fSpoolFileHeader.page_count++;
	fSpoolFile->Seek(0, SEEK_END);
	if (fCurrentPageHeaderOffset) {
		// update last written page_header
		fCurrentPageHeader->next_page = fSpoolFile->Position();
		fSpoolFile->Seek(fCurrentPageHeaderOffset, SEEK_SET);
		fSpoolFile->Write(fCurrentPageHeader, sizeof(_page_header_));
		fSpoolFile->Seek(fCurrentPageHeader->next_page, SEEK_SET);
	}
 
	_NewPage();
}
 
 
bool
BPrintJob::CanContinue()
{
	// Check if our local error storage is still B_OK
	return fError == B_OK && !fAbort;
}
 
 
void
BPrintJob::DrawView(BView* view, BRect rect, BPoint where)
{
	if (fSpoolFile == NULL)
		return;
 
	if (view == NULL)
		return;
 
	if (view->LockLooper()) {
		BPicture picture;
		_RecurseView(view, B_ORIGIN - rect.LeftTop(), &picture, rect);
		_AddPicture(picture, rect, where);
		view->UnlockLooper();
	}
}
 
 
BMessage*
BPrintJob::Settings()
{
	if (fSetupMessage == NULL)
		return NULL;
 
	return new BMessage(*fSetupMessage);
}
 
 
void
BPrintJob::SetSettings(BMessage* message)
{
	if (message != NULL)
		_HandlePrintSetup(message);
 
	delete fSetupMessage;
	fSetupMessage = message;
}
 
 
bool
BPrintJob::IsSettingsMessageValid(BMessage* message) const
{
	char* printerName = _GetCurrentPrinterName();
	if (printerName == NULL)
		return false;
 
	const char* name = NULL;
	// The passed message is valid if it contains the right printer name.
	bool valid = message != NULL
		&& message->FindString("printer_name", &name) == B_OK
		&& strcmp(printerName, name) == 0;
 
	free(printerName);
	return valid;
}
 
 
// Either SetSettings() or ConfigPage() has to be called prior
// to any of the getters otherwise they return undefined values.
BRect
BPrintJob::PaperRect()
{
	if (fDefaultSetupMessage == NULL)
		_LoadDefaultSettings();
 
	return fPaperSize;
}
 
 
BRect
BPrintJob::PrintableRect()
{
	if (fDefaultSetupMessage == NULL)
		_LoadDefaultSettings();
 
	return fUsableSize;
}
 
 
void
BPrintJob::GetResolution(int32* xdpi, int32* ydpi)
{
	if (fDefaultSetupMessage == NULL)
		_LoadDefaultSettings();
 
	if (xdpi != NULL)
		*xdpi = fXResolution;
 
	if (ydpi != NULL)
		*ydpi = fYResolution;
}
 
 
int32
BPrintJob::FirstPage()
{
	return fFirstPage;
}
 
 
int32
BPrintJob::LastPage()
{
	return fLastPage;
}
 
 
int32
BPrintJob::PrinterType(void*) const
{
	BMessenger printServer;
	if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
		return B_COLOR_PRINTER; // default
 
	BMessage reply;
	BMessage message(PSRV_GET_ACTIVE_PRINTER);
	printServer.SendMessage(&message, &reply);
 
	int32 type;
	if (reply.FindInt32("color", &type) != B_OK)
		return B_COLOR_PRINTER; // default
 
	return type;
}
 
 
// #pragma mark - private
 
 
void
BPrintJob::_RecurseView(BView* view, BPoint origin, BPicture* picture,
	BRect rect)
{
	ASSERT(picture != NULL);
 
	BRegion region;
	region.Set(BRect(rect.left, rect.top, rect.right, rect.bottom));
	view->fState->print_rect = rect;
 
	view->AppendToPicture(picture);
	view->PushState();
	view->SetOrigin(origin);
	view->ConstrainClippingRegion(&region);
 
	if (view->ViewColor() != B_TRANSPARENT_COLOR) {
		rgb_color highColor = view->HighColor();
		view->SetHighColor(view->ViewColor());
		view->FillRect(rect);
		view->SetHighColor(highColor);
	}
 
	if ((view->Flags() & B_WILL_DRAW) != 0) {
		view->fIsPrinting = true;
		view->Draw(rect);
		view->fIsPrinting = false;
	}
 
	view->PopState();
	view->EndPicture();
 
	BView* child = view->ChildAt(0);
	while (child != NULL) {
		if (!child->IsHidden()) {
			BPoint leftTop(view->Bounds().LeftTop() + child->Frame().LeftTop());
			BRect printRect(rect.OffsetToCopy(rect.LeftTop() - leftTop)
				& child->Bounds());
			if (printRect.IsValid())
				_RecurseView(child, origin + leftTop, picture, printRect);
		}
		child = child->NextSibling();
	}
 
	if ((view->Flags() & B_DRAW_ON_CHILDREN) != 0) {
		view->AppendToPicture(picture);
		view->PushState();
		view->SetOrigin(origin);
		view->ConstrainClippingRegion(&region);
		view->fIsPrinting = true;
		view->DrawAfterChildren(rect);
		view->fIsPrinting = false;
		view->PopState();
		view->EndPicture();
	}
}
 
 
void
BPrintJob::_GetMangledName(char* buffer, size_t bufferSize) const
{
	snprintf(buffer, bufferSize, "%s@%" B_PRId64, fPrintJobName,
		system_time() / 1000);
}
 
 
void
BPrintJob::_HandlePageSetup(BMessage* setup)
{
	setup->FindRect(PSRV_FIELD_PRINTABLE_RECT, &fUsableSize);
	setup->FindRect(PSRV_FIELD_PAPER_RECT, &fPaperSize);
 
	// TODO verify data type (taken from libprint)
	int64 valueInt64;
	if (setup->FindInt64(PSRV_FIELD_XRES, &valueInt64) == B_OK)
		fXResolution = (short)valueInt64;
 
	if (setup->FindInt64(PSRV_FIELD_YRES, &valueInt64) == B_OK)
		fYResolution = (short)valueInt64;
}
 
 
bool
BPrintJob::_HandlePrintSetup(BMessage* message)
{
	_HandlePageSetup(message);
 
	bool valid = true;
	if (message->FindInt32(PSRV_FIELD_FIRST_PAGE, &fFirstPage) != B_OK)
		valid = false;
 
	if (message->FindInt32(PSRV_FIELD_LAST_PAGE, &fLastPage) != B_OK)
		valid = false;
 
	return valid;
}
 
 
void
BPrintJob::_NewPage()
{
	// init, write new page_header
	fCurrentPageHeader->next_page = 0;
	fCurrentPageHeader->number_of_pictures = 0;
	fCurrentPageHeaderOffset = fSpoolFile->Position();
	fSpoolFile->Write(fCurrentPageHeader, sizeof(_page_header_));
}
 
 
void
BPrintJob::_EndLastPage()
{
	if (!fSpoolFile)
		return;
 
	if (fCurrentPageHeader->number_of_pictures == 0)
		return;
 
	fSpoolFileHeader.page_count++;
	fSpoolFile->Seek(0, SEEK_END);
	if (fCurrentPageHeaderOffset) {
		fCurrentPageHeader->next_page = 0;
		fSpoolFile->Seek(fCurrentPageHeaderOffset, SEEK_SET);
		fSpoolFile->Write(fCurrentPageHeader, sizeof(_page_header_));
		fSpoolFile->Seek(0, SEEK_END);
	}
}
 
 
void
BPrintJob::_AddSetupSpec()
{
	fSetupMessage->Flatten(fSpoolFile);
}
 
 
void
BPrintJob::_AddPicture(BPicture& picture, BRect& rect, BPoint& where)
{
	ASSERT(fSpoolFile != NULL);
 
	fCurrentPageHeader->number_of_pictures++;
	fSpoolFile->Write(&where, sizeof(BRect));
	fSpoolFile->Write(&rect, sizeof(BPoint));
	picture.Flatten(fSpoolFile);
}
 
 
/*!	Returns a copy of the applications default printer name or NULL if it
	could not be obtained. Caller is responsible to free the string using
	free().
*/
char*
BPrintJob::_GetCurrentPrinterName() const
{
	BMessenger printServer;
	if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
		return NULL;
 
	const char* printerName = NULL;
 
	BMessage reply;
	BMessage message(PSRV_GET_ACTIVE_PRINTER);
	if (printServer.SendMessage(&message, &reply) == B_OK)
		reply.FindString("printer_name", &printerName);
 
	if (printerName == NULL)
		return NULL;
 
	return strdup(printerName);
}
 
 
void
BPrintJob::_LoadDefaultSettings()
{
	BMessenger printServer;
	if (PrintServerMessenger::GetPrintServerMessenger(printServer) != B_OK)
		return;
 
	BMessage message(PSRV_GET_DEFAULT_SETTINGS);
	BMessage* reply = new BMessage;
 
	printServer.SendMessage(&message, reply);
 
	// Only override our settings if we don't have any settings yet
	if (fSetupMessage == NULL)
		_HandlePrintSetup(reply);
 
	delete fDefaultSetupMessage;
	fDefaultSetupMessage = reply;
}
 
 
void BPrintJob::_ReservedPrintJob1() {}
void BPrintJob::_ReservedPrintJob2() {}
void BPrintJob::_ReservedPrintJob3() {}
void BPrintJob::_ReservedPrintJob4() {}
 
 
// #pragma mark -- PrintServerMessenger
 
 
namespace BPrivate {
 
 
PrintServerMessenger::PrintServerMessenger(uint32 what, BMessage *input)
	:
	fWhat(what),
	fInput(input),
	fRequest(NULL),
	fResult(NULL),
	fThreadCompleted(-1),
	fHiddenApplicationModalWindow(NULL)
{
	RejectUserInput();
}
 
 
PrintServerMessenger::~PrintServerMessenger()
{
	DeleteSemaphore();
		// in case SendRequest could not start the thread
	delete fRequest; fRequest = NULL;
	AllowUserInput();
}
 
 
void
PrintServerMessenger::RejectUserInput()
{
	fHiddenApplicationModalWindow = new BAlert("bogus", "app_modal", "OK");
	fHiddenApplicationModalWindow->DefaultButton()->SetEnabled(false);
	fHiddenApplicationModalWindow->SetDefaultButton(NULL);
	fHiddenApplicationModalWindow->SetFlags(fHiddenApplicationModalWindow->Flags() | B_CLOSE_ON_ESCAPE);
	fHiddenApplicationModalWindow->MoveTo(-65000, -65000);
	fHiddenApplicationModalWindow->Go(NULL);
}
 
 
void
PrintServerMessenger::AllowUserInput()
{
	fHiddenApplicationModalWindow->Lock();
	fHiddenApplicationModalWindow->Quit();
}
 
 
void
PrintServerMessenger::DeleteSemaphore()
{
	if (fThreadCompleted >= B_OK) {
		sem_id id = fThreadCompleted;
		fThreadCompleted = -1;
		delete_sem(id);
	}
}
 
 
status_t
PrintServerMessenger::SendRequest()
{
	fThreadCompleted = create_sem(0, "print_server_messenger_sem");
	if (fThreadCompleted < B_OK)
		return B_ERROR;
 
	thread_id id = spawn_thread(MessengerThread, "async_request",
		B_NORMAL_PRIORITY, this);
	if (id <= 0 || resume_thread(id) != B_OK)
		return B_ERROR;
 
	// Get the originating window, if it exists
	BWindow* window = dynamic_cast<BWindow*>(
		BLooper::LooperForThread(find_thread(NULL)));
	if (window != NULL) {
		status_t err;
		while (true) {
			do {
				err = acquire_sem_etc(fThreadCompleted, 1, B_RELATIVE_TIMEOUT,
					50000);
			// We've (probably) had our time slice taken away from us
			} while (err == B_INTERRUPTED);
 
			// Semaphore was finally nuked in SetResult(BMessage *)
			if (err == B_BAD_SEM_ID)
				break;
			window->UpdateIfNeeded();
		}
	} else {
		// No window to update, so just hang out until we're done.
		while (acquire_sem(fThreadCompleted) == B_INTERRUPTED);
	}
 
	status_t status;
	wait_for_thread(id, &status);
 
	return Result() != NULL ? B_OK : B_ERROR;
}
 
 
BMessage*
PrintServerMessenger::Request()
{
	if (fRequest != NULL)
		return fRequest;
 
	if (fInput != NULL) {
		fRequest = new BMessage(*fInput);
		fRequest->what = fWhat;
	} else
		fRequest = new BMessage(fWhat);
 
	return fRequest;
}
 
 
void
PrintServerMessenger::SetResult(BMessage* result)
{
	fResult = result;
	DeleteSemaphore();
	// terminate loop in thread spawned by SendRequest
}
 
 
status_t
PrintServerMessenger::GetPrintServerMessenger(BMessenger& messenger)
{
	messenger = BMessenger(PSRV_SIGNATURE_TYPE);
	return messenger.IsValid() ? B_OK : B_ERROR;
}
 
 
status_t
PrintServerMessenger::MessengerThread(void* data)
{
	PrintServerMessenger* messenger = static_cast<PrintServerMessenger*>(data);
 
	BMessenger printServer;
	if (messenger->GetPrintServerMessenger(printServer) != B_OK) {
		ShowError(B_TRANSLATE("Print Server is not responding."));
		messenger->SetResult(NULL);
		return B_ERROR;
	}
 
	BMessage* request = messenger->Request();
	if (request == NULL) {
		messenger->SetResult(NULL);
		return B_ERROR;
	}
 
 
	BMessage reply;
	if (printServer.SendMessage(request, &reply) != B_OK
		|| reply.what != 'okok' ) {
		messenger->SetResult(NULL);
		return B_ERROR;
	}
 
	messenger->SetResult(new BMessage(reply));
	return B_OK;
}
 
 
}	// namespace BPrivate

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.