/*
 * Copyright 2001-2015, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Ithamar R. Adema
 *		Michael Pfeiffer
 */
 
 
#include "PrintServerApp.h"
 
#include <stdio.h>
#include <unistd.h>
 
#include <Alert.h>
#include <Autolock.h>
#include <Catalog.h>
#include <Directory.h>
#include <File.h>
#include <FindDirectory.h>
#include <image.h>
#include <Locale.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <Roster.h>
#include <PrintJob.h>
#include <String.h>
 
#include "BeUtils.h"
#include "Printer.h"
#include "pr_server.h"
#include "Transport.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PrintServerApp"
 
 
typedef struct _printer_data {
	char defaultPrinterName[256];
} printer_data_t;
 
 
static const char* kSettingsName = "print_server_settings";
 
BLocker *gLock = NULL;
 
 
/*!	Main entry point of print_server.
 
	@returns B_OK if application was started, or an errorcode if
			the application failed to start.
*/
int
main()
{
	gLock = new BLocker();
 
	status_t status = B_OK;
	PrintServerApp printServer(&status);
	if (status == B_OK)
		printServer.Run();
 
	delete gLock;
	return status;
}
 
 
/*!	Constructor for print_server's application class. Retrieves the
	name of the default printer from storage, caches the icons for
	a selected printer.
 
	@param err Pointer to status_t for storing result of application
			initialisation.
	@see BApplication
*/
PrintServerApp::PrintServerApp(status_t* err)
	:
	Inherited(PSRV_SIGNATURE_TYPE, true, err),
	fDefaultPrinter(NULL),
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
	fIconSize(0),
	fSelectedIcon(NULL),
#else
	fSelectedIconMini(NULL),
	fSelectedIconLarge(NULL),
#endif
	fReferences(0),
	fHasReferences(0),
	fUseConfigWindow(true),
	fFolder(NULL)
{
	fSettings = Settings::GetSettings();
	LoadSettings();
 
	if (*err != B_OK)
		return;
 
	fHasReferences = create_sem(1, "has_references");
 
	// Build list of transport addons
	Transport::Scan(B_USER_NONPACKAGED_ADDONS_DIRECTORY);
	Transport::Scan(B_USER_ADDONS_DIRECTORY);
	Transport::Scan(B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY);
	Transport::Scan(B_SYSTEM_ADDONS_DIRECTORY);
 
	SetupPrinterList();
	RetrieveDefaultPrinter();
 
	// Cache icons for selected printer
	BMimeType type(PSRV_PRINTER_FILETYPE);
	type.GetIcon(&fSelectedIcon, &fIconSize);
 
	PostMessage(PSRV_PRINT_SPOOLED_JOB);
		// Start handling of spooled files
}
 
 
PrintServerApp::~PrintServerApp()
{
	SaveSettings();
	delete fSettings;
}
 
 
bool
PrintServerApp::QuitRequested()
{
	// don't quit when user types Command+Q!
	BMessage* m = CurrentMessage();
	bool shortcut;
	if (m != NULL && m->FindBool("shortcut", &shortcut) == B_OK && shortcut)
		return false;
 
	if (!Inherited::QuitRequested())
		return false;
 
	// Stop watching the folder
	delete fFolder; fFolder = NULL;
 
	// Release all printers
	Printer* printer;
	while ((printer = Printer::At(0)) != NULL) {
		printer->AbortPrintThread();
		UnregisterPrinter(printer);
	}
 
	// Wait for printers
	if (fHasReferences > 0) {
		acquire_sem(fHasReferences);
		delete_sem(fHasReferences);
		fHasReferences = 0;
	}
 
	delete [] fSelectedIcon;
	fSelectedIcon = NULL;
 
	return true;
}
 
 
void
PrintServerApp::Acquire()
{
	if (atomic_add(&fReferences, 1) == 0)
		acquire_sem(fHasReferences);
}
 
 
void
PrintServerApp::Release()
{
	if (atomic_add(&fReferences, -1) == 1)
		release_sem(fHasReferences);
}
 
 
void
PrintServerApp::RegisterPrinter(BDirectory* printer)
{
	BString transport, address, connection, state;
 
	if (printer->ReadAttrString(PSRV_PRINTER_ATTR_TRANSPORT, &transport) == B_OK
		&& printer->ReadAttrString(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, &address)
			== B_OK
		&& printer->ReadAttrString(PSRV_PRINTER_ATTR_CNX, &connection) == B_OK
		&& printer->ReadAttrString(PSRV_PRINTER_ATTR_STATE, &state) == B_OK
		&& state == "free") {
 		BAutolock lock(gLock);
		if (lock.IsLocked()) {
			// check if printer is already registered
			node_ref node;
			if (printer->GetNodeRef(&node) != B_OK)
				return;
 
			if (Printer::Find(&node) != NULL)
				return;
 
			// register new printer
			Resource* resource = fResourceManager.Allocate(transport.String(),
				address.String(), connection.String());
			AddHandler(new Printer(printer, resource));
		 	Acquire();
		}
	}
}
 
 
void
PrintServerApp::UnregisterPrinter(Printer* printer)
{
	RemoveHandler(printer);
	Printer::Remove(printer);
	printer->Release();
}
 
 
void
PrintServerApp::NotifyPrinterDeletion(Printer* printer)
{
	BAutolock lock(gLock);
	if (lock.IsLocked()) {
		fResourceManager.Free(printer->GetResource());
		Release();
	}
}
 
 
void
PrintServerApp::EntryCreated(node_ref* node, entry_ref* entry)
{
	BEntry printer(entry);
	if (printer.InitCheck() == B_OK && printer.IsDirectory()) {
		BDirectory dir(&printer);
		if (dir.InitCheck() == B_OK) RegisterPrinter(&dir);
	}
}
 
 
void
PrintServerApp::EntryRemoved(node_ref* node)
{
	Printer* printer = Printer::Find(node);
	if (printer) {
		if (printer == fDefaultPrinter)
			fDefaultPrinter = NULL;
		UnregisterPrinter(printer);
	}
}
 
 
void
PrintServerApp::AttributeChanged(node_ref* node)
{
	BDirectory printer(node);
	if (printer.InitCheck() == B_OK)
		RegisterPrinter(&printer);
}
 
 
/*!	This method builds the internal list of printers from disk. It
	also installs a node monitor to be sure that the list keeps
	updated with the definitions on disk.
 
	@return B_OK if successful, or an errorcode if failed.
*/
status_t
PrintServerApp::SetupPrinterList()
{
	// Find directory containing printer definition nodes
	BPath path;
	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path);
	if (status != B_OK)
		return status;
 
	// Directory has to exist in order to watch it
	mode_t mode = 0777;
	create_directory(path.Path(), mode);
 
	BDirectory dir(path.Path());
	status = dir.InitCheck();
	if (status != B_OK)
		return status;
 
	// Register printer definition nodes
	BEntry entry;
	while(dir.GetNextEntry(&entry) == B_OK) {
		if (!entry.IsDirectory())
			continue;
 
		BDirectory node(&entry);
		BNodeInfo info(&node);
		char buffer[256];
		if (info.GetType(buffer) == B_OK
			&& strcmp(buffer, PSRV_PRINTER_FILETYPE) == 0) {
			RegisterPrinter(&node);
		}
	}
 
	// Now we are ready to start node watching
	fFolder = new FolderWatcher(this, dir, true);
	fFolder->SetListener(this);
 
	return B_OK;
}
 
 
/*!	Message handling method for print_server application class.
 
	@param msg Actual message sent to application class.
*/
void
PrintServerApp::MessageReceived(BMessage* msg)
{
	switch(msg->what) {
		case PSRV_GET_ACTIVE_PRINTER:
		case PSRV_MAKE_PRINTER_ACTIVE_QUIETLY:
		case PSRV_MAKE_PRINTER_ACTIVE:
		case PSRV_MAKE_PRINTER:
		case PSRV_SHOW_PAGE_SETUP:
		case PSRV_SHOW_PRINT_SETUP:
		case PSRV_PRINT_SPOOLED_JOB:
		case PSRV_GET_DEFAULT_SETTINGS:
			Handle_BeOSR5_Message(msg);
			break;
 
		case B_GET_PROPERTY:
		case B_SET_PROPERTY:
		case B_CREATE_PROPERTY:
		case B_DELETE_PROPERTY:
		case B_COUNT_PROPERTIES:
		case B_EXECUTE_PROPERTY:
			HandleScriptingCommand(msg);
			break;
 
		default:
			Inherited::MessageReceived(msg);
	}
}
 
 
/*!	Creates printer definition/spool directory. It sets the
	attributes of the directory to the values passed and calls
	the driver's add_printer method to handle any configuration
	needed.
 
	@param printerName Name of printer to create.
	@param driverName Name of driver to use for this printer.
	@param connection "Local" or "Network".
	@param transportName Name of transport driver to use.
	@param transportPath Configuration data for transport driver.
*/
status_t
PrintServerApp::CreatePrinter(const char* printerName, const char* driverName,
	const char* connection, const char* transportName,
	const char* transportPath)
{
	// Find directory containing printer definitions
	BPath path;
	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path, true,
		NULL);
	if (status != B_OK)
		return status;
 
	// Create our printer definition/spool directory
	BDirectory printersDir(path.Path());
	BDirectory printer;
 
	status = printersDir.CreateDirectory(printerName, &printer);
	if (status == B_FILE_EXISTS) {
		printer.SetTo(&printersDir, printerName);
 
		BString info;
		char type[B_MIME_TYPE_LENGTH];
		BNodeInfo(&printer).GetType(type);
		if (strcmp(PSRV_PRINTER_FILETYPE, type) == 0) {
			BPath path;
			if (Printer::FindPathToDriver(printerName, &path) == B_OK) {
				if (fDefaultPrinter) {
					// the printer exists, but is not the default printer
					if (strcmp(fDefaultPrinter->Name(), printerName) != 0)
						status = B_OK;
					return status;
				}
				// the printer exists, but no default at all
				return B_OK;
			} else {
				info.SetTo(B_TRANSLATE(
					"A printer with that name already exists, "
					"but its driver could not be found! Replace?"));
			}
		} else {
			info.SetTo(B_TRANSLATE(
				"A printer with that name already exists, "
				"but it's not usable at all! Replace?"));
		}
 
		if (info.Length() != 0) {
			BAlert *alert = new BAlert("Info", info.String(),
				B_TRANSLATE("Cancel"), B_TRANSLATE("OK"));
			alert->SetShortcut(0, B_ESCAPE);
			if (alert->Go() == 0)
				return status;
		}
	} else if (status != B_OK) {
		return status;
	}
 
	// Set its type to a printer
	BNodeInfo info(&printer);
	info.SetType(PSRV_PRINTER_FILETYPE);
 
	// Store the settings in its attributes
	printer.WriteAttr(PSRV_PRINTER_ATTR_PRT_NAME, B_STRING_TYPE, 0, printerName,
		::strlen(printerName) + 1);
	printer.WriteAttr(PSRV_PRINTER_ATTR_DRV_NAME, B_STRING_TYPE, 0, driverName,
		::strlen(driverName) + 1);
	printer.WriteAttr(PSRV_PRINTER_ATTR_TRANSPORT, B_STRING_TYPE, 0,
		transportName, ::strlen(transportName) + 1);
	printer.WriteAttr(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, B_STRING_TYPE, 0,
		transportPath, ::strlen(transportPath) + 1);
	printer.WriteAttr(PSRV_PRINTER_ATTR_CNX, B_STRING_TYPE, 0, connection,
		::strlen(connection) + 1);
 
	status = Printer::ConfigurePrinter(driverName, printerName);
	if (status == B_OK) {
		// Notify printer driver that a new printer definition node
		// has been created.
		printer.WriteAttr(PSRV_PRINTER_ATTR_STATE, B_STRING_TYPE, 0, "free",
			::strlen("free")+1);
	}
 
	if (status != B_OK) {
		BEntry entry;
		if (printer.GetEntry(&entry) == B_OK)
			entry.Remove();
	}
 
	return status;
}
 
 
/*!	Makes a new printer the active printer. This is done simply
	by changing our class attribute fDefaultPrinter, and changing
	the icon of the BNode for the printer. Ofcourse, we need to
	change the icon of the "old" default printer first back to a
	"non-active" printer icon first.
 
	@param printerName Name of the new active printer.
	@return B_OK on success, or error code otherwise.
*/
status_t
PrintServerApp::SelectPrinter(const char* printerName)
{
	// Find the node of the "old" default printer
	BNode node;
	if (fDefaultPrinter != NULL
		&& FindPrinterNode(fDefaultPrinter->Name(), node) == B_OK) {
		// and remove the custom icon
		BNodeInfo info(&node);
		info.SetIcon(NULL, B_MINI_ICON);
		info.SetIcon(NULL, B_LARGE_ICON);
	}
 
	// Find the node for the new default printer
	status_t status = FindPrinterNode(printerName, node);
	if (status == B_OK) {
		// and add the custom icon
		BNodeInfo info(&node);
		info.SetIcon(fSelectedIcon, fIconSize);
	}
 
	fDefaultPrinter = Printer::Find(printerName);
	StoreDefaultPrinter();
		// update our pref file
	be_roster->Broadcast(new BMessage(B_PRINTER_CHANGED));
 
	return status;
}
 
 
//! Handles calling the printer drivers for printing a spooled job.
void
PrintServerApp::HandleSpooledJobs()
{
	const int n = Printer::CountPrinters();
	for (int i = 0; i < n; i ++) {
		Printer* printer = Printer::At(i);
		printer->HandleSpooledJob();
	}
}
 
 
/*!	Loads the currently selected printer from a private settings
	file.
 
	@return Error code on failore, or B_OK if all went fine.
*/
status_t
PrintServerApp::RetrieveDefaultPrinter()
{
	fDefaultPrinter = Printer::Find(fSettings->DefaultPrinter());
	return B_OK;
}
 
 
/*!	Stores the currently selected printer in a private settings
	file.
 
	@return Error code on failore, or B_OK if all went fine.
*/
status_t
PrintServerApp::StoreDefaultPrinter()
{
	if (fDefaultPrinter)
		fSettings->SetDefaultPrinter(fDefaultPrinter->Name());
	else
		fSettings->SetDefaultPrinter("");
	return B_OK;
}
 
 
/*!	Find the BNode representing the specified printer. It searches
	*only* in the users printer definitions.
 
	@param name Name of the printer to look for.
	@param node BNode to set to the printer definition node.
	@return B_OK if found, an error code otherwise.
*/
status_t
PrintServerApp::FindPrinterNode(const char* name, BNode& node)
{
	// Find directory containing printer definitions
	BPath path;
	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path, true,
		NULL);
	if (status != B_OK)
		return status;
 
	path.Append(name);
	return node.SetTo(path.Path());
}
 
 
bool 
PrintServerApp::OpenSettings(BFile& file, const char* name, bool forReading)
{
	BPath path;
	uint32 openMode = forReading ? B_READ_ONLY : B_CREATE_FILE | B_ERASE_FILE
		| B_WRITE_ONLY;
	return find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK
		&& path.Append(name) == B_OK
		&& file.SetTo(path.Path(), openMode) == B_OK;
}
 
 
void 
PrintServerApp::LoadSettings()
{
	BFile file;
	if (OpenSettings(file, kSettingsName, true)) {
		fSettings->Load(&file);
		fUseConfigWindow = fSettings->UseConfigWindow();
	}
}
 
 
void 
PrintServerApp::SaveSettings()
{
	BFile file;
	if (OpenSettings(file, kSettingsName, false)) {
		fSettings->SetUseConfigWindow(fUseConfigWindow);
		fSettings->Save(&file);
	}
}

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.