1/*
2 * Copyright 2001-2015, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ithamar R. Adema
7 *		Michael Pfeiffer
8 */
9
10
11#include "PrintServerApp.h"
12
13#include <stdio.h>
14#include <unistd.h>
15
16#include <Alert.h>
17#include <Autolock.h>
18#include <Catalog.h>
19#include <Directory.h>
20#include <File.h>
21#include <FindDirectory.h>
22#include <image.h>
23#include <Locale.h>
24#include <Mime.h>
25#include <NodeInfo.h>
26#include <NodeMonitor.h>
27#include <Path.h>
28#include <Roster.h>
29#include <PrintJob.h>
30#include <String.h>
31
32#include "BeUtils.h"
33#include "Printer.h"
34#include "pr_server.h"
35#include "Transport.h"
36
37
38#undef B_TRANSLATION_CONTEXT
39#define B_TRANSLATION_CONTEXT "PrintServerApp"
40
41
42typedef struct _printer_data {
43	char defaultPrinterName[256];
44} printer_data_t;
45
46
47static const char* kSettingsName = "print_server_settings";
48
49BLocker *gLock = NULL;
50
51
52/*!	Main entry point of print_server.
53
54	@returns B_OK if application was started, or an errorcode if
55			the application failed to start.
56*/
57int
58main()
59{
60	gLock = new BLocker();
61
62	status_t status = B_OK;
63	PrintServerApp printServer(&status);
64	if (status == B_OK)
65		printServer.Run();
66
67	delete gLock;
68	return status;
69}
70
71
72/*!	Constructor for print_server's application class. Retrieves the
73	name of the default printer from storage, caches the icons for
74	a selected printer.
75
76	@param err Pointer to status_t for storing result of application
77			initialisation.
78	@see BApplication
79*/
80PrintServerApp::PrintServerApp(status_t* err)
81	:
82	Inherited(PSRV_SIGNATURE_TYPE, true, err),
83	fDefaultPrinter(NULL),
84#ifdef HAIKU_TARGET_PLATFORM_HAIKU
85	fIconSize(0),
86	fSelectedIcon(NULL),
87#else
88	fSelectedIconMini(NULL),
89	fSelectedIconLarge(NULL),
90#endif
91	fReferences(0),
92	fHasReferences(0),
93	fUseConfigWindow(true),
94	fFolder(NULL)
95{
96	fSettings = Settings::GetSettings();
97	LoadSettings();
98
99	if (*err != B_OK)
100		return;
101
102	fHasReferences = create_sem(1, "has_references");
103
104	// Build list of transport addons
105	Transport::Scan(B_USER_NONPACKAGED_ADDONS_DIRECTORY);
106	Transport::Scan(B_USER_ADDONS_DIRECTORY);
107	Transport::Scan(B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY);
108	Transport::Scan(B_SYSTEM_ADDONS_DIRECTORY);
109
110	SetupPrinterList();
111	RetrieveDefaultPrinter();
112
113	// Cache icons for selected printer
114	BMimeType type(PSRV_PRINTER_FILETYPE);
115	type.GetIcon(&fSelectedIcon, &fIconSize);
116
117	PostMessage(PSRV_PRINT_SPOOLED_JOB);
118		// Start handling of spooled files
119}
120
121
122PrintServerApp::~PrintServerApp()
123{
124	SaveSettings();
125	delete fSettings;
126}
127
128
129bool
130PrintServerApp::QuitRequested()
131{
132	// don't quit when user types Command+Q!
133	BMessage* m = CurrentMessage();
134	bool shortcut;
135	if (m != NULL && m->FindBool("shortcut", &shortcut) == B_OK && shortcut)
136		return false;
137
138	if (!Inherited::QuitRequested())
139		return false;
140
141	// Stop watching the folder
142	delete fFolder; fFolder = NULL;
143
144	// Release all printers
145	Printer* printer;
146	while ((printer = Printer::At(0)) != NULL) {
147		printer->AbortPrintThread();
148		UnregisterPrinter(printer);
149	}
150
151	// Wait for printers
152	if (fHasReferences > 0) {
153		acquire_sem(fHasReferences);
154		delete_sem(fHasReferences);
155		fHasReferences = 0;
156	}
157
158	delete [] fSelectedIcon;
159	fSelectedIcon = NULL;
160
161	return true;
162}
163
164
165void
166PrintServerApp::Acquire()
167{
168	if (atomic_add(&fReferences, 1) == 0)
169		acquire_sem(fHasReferences);
170}
171
172
173void
174PrintServerApp::Release()
175{
176	if (atomic_add(&fReferences, -1) == 1)
177		release_sem(fHasReferences);
178}
179
180
181void
182PrintServerApp::RegisterPrinter(BDirectory* printer)
183{
184	BString transport, address, connection, state;
185
186	if (printer->ReadAttrString(PSRV_PRINTER_ATTR_TRANSPORT, &transport) == B_OK
187		&& printer->ReadAttrString(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, &address)
188			== B_OK
189		&& printer->ReadAttrString(PSRV_PRINTER_ATTR_CNX, &connection) == B_OK
190		&& printer->ReadAttrString(PSRV_PRINTER_ATTR_STATE, &state) == B_OK
191		&& state == "free") {
192 		BAutolock lock(gLock);
193		if (lock.IsLocked()) {
194			// check if printer is already registered
195			node_ref node;
196			if (printer->GetNodeRef(&node) != B_OK)
197				return;
198
199			if (Printer::Find(&node) != NULL)
200				return;
201
202			// register new printer
203			Resource* resource = fResourceManager.Allocate(transport.String(),
204				address.String(), connection.String());
205			AddHandler(new Printer(printer, resource));
206		 	Acquire();
207		}
208	}
209}
210
211
212void
213PrintServerApp::UnregisterPrinter(Printer* printer)
214{
215	RemoveHandler(printer);
216	Printer::Remove(printer);
217	printer->Release();
218}
219
220
221void
222PrintServerApp::NotifyPrinterDeletion(Printer* printer)
223{
224	BAutolock lock(gLock);
225	if (lock.IsLocked()) {
226		fResourceManager.Free(printer->GetResource());
227		Release();
228	}
229}
230
231
232void
233PrintServerApp::EntryCreated(node_ref* node, entry_ref* entry)
234{
235	BEntry printer(entry);
236	if (printer.InitCheck() == B_OK && printer.IsDirectory()) {
237		BDirectory dir(&printer);
238		if (dir.InitCheck() == B_OK) RegisterPrinter(&dir);
239	}
240}
241
242
243void
244PrintServerApp::EntryRemoved(node_ref* node)
245{
246	Printer* printer = Printer::Find(node);
247	if (printer) {
248		if (printer == fDefaultPrinter)
249			fDefaultPrinter = NULL;
250		UnregisterPrinter(printer);
251	}
252}
253
254
255void
256PrintServerApp::AttributeChanged(node_ref* node)
257{
258	BDirectory printer(node);
259	if (printer.InitCheck() == B_OK)
260		RegisterPrinter(&printer);
261}
262
263
264/*!	This method builds the internal list of printers from disk. It
265	also installs a node monitor to be sure that the list keeps
266	updated with the definitions on disk.
267
268	@return B_OK if successful, or an errorcode if failed.
269*/
270status_t
271PrintServerApp::SetupPrinterList()
272{
273	// Find directory containing printer definition nodes
274	BPath path;
275	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path);
276	if (status != B_OK)
277		return status;
278
279	// Directory has to exist in order to watch it
280	mode_t mode = 0777;
281	create_directory(path.Path(), mode);
282
283	BDirectory dir(path.Path());
284	status = dir.InitCheck();
285	if (status != B_OK)
286		return status;
287
288	// Register printer definition nodes
289	BEntry entry;
290	while(dir.GetNextEntry(&entry) == B_OK) {
291		if (!entry.IsDirectory())
292			continue;
293
294		BDirectory node(&entry);
295		BNodeInfo info(&node);
296		char buffer[256];
297		if (info.GetType(buffer) == B_OK
298			&& strcmp(buffer, PSRV_PRINTER_FILETYPE) == 0) {
299			RegisterPrinter(&node);
300		}
301	}
302
303	// Now we are ready to start node watching
304	fFolder = new FolderWatcher(this, dir, true);
305	fFolder->SetListener(this);
306
307	return B_OK;
308}
309
310
311/*!	Message handling method for print_server application class.
312
313	@param msg Actual message sent to application class.
314*/
315void
316PrintServerApp::MessageReceived(BMessage* msg)
317{
318	switch(msg->what) {
319		case PSRV_GET_ACTIVE_PRINTER:
320		case PSRV_MAKE_PRINTER_ACTIVE_QUIETLY:
321		case PSRV_MAKE_PRINTER_ACTIVE:
322		case PSRV_MAKE_PRINTER:
323		case PSRV_SHOW_PAGE_SETUP:
324		case PSRV_SHOW_PRINT_SETUP:
325		case PSRV_PRINT_SPOOLED_JOB:
326		case PSRV_GET_DEFAULT_SETTINGS:
327			Handle_BeOSR5_Message(msg);
328			break;
329
330		case B_GET_PROPERTY:
331		case B_SET_PROPERTY:
332		case B_CREATE_PROPERTY:
333		case B_DELETE_PROPERTY:
334		case B_COUNT_PROPERTIES:
335		case B_EXECUTE_PROPERTY:
336			HandleScriptingCommand(msg);
337			break;
338
339		default:
340			Inherited::MessageReceived(msg);
341	}
342}
343
344
345/*!	Creates printer definition/spool directory. It sets the
346	attributes of the directory to the values passed and calls
347	the driver's add_printer method to handle any configuration
348	needed.
349
350	@param printerName Name of printer to create.
351	@param driverName Name of driver to use for this printer.
352	@param connection "Local" or "Network".
353	@param transportName Name of transport driver to use.
354	@param transportPath Configuration data for transport driver.
355*/
356status_t
357PrintServerApp::CreatePrinter(const char* printerName, const char* driverName,
358	const char* connection, const char* transportName,
359	const char* transportPath)
360{
361	// Find directory containing printer definitions
362	BPath path;
363	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path, true,
364		NULL);
365	if (status != B_OK)
366		return status;
367
368	// Create our printer definition/spool directory
369	BDirectory printersDir(path.Path());
370	BDirectory printer;
371
372	status = printersDir.CreateDirectory(printerName, &printer);
373	if (status == B_FILE_EXISTS) {
374		printer.SetTo(&printersDir, printerName);
375
376		BString info;
377		char type[B_MIME_TYPE_LENGTH];
378		BNodeInfo(&printer).GetType(type);
379		if (strcmp(PSRV_PRINTER_FILETYPE, type) == 0) {
380			BPath path;
381			if (Printer::FindPathToDriver(printerName, &path) == B_OK) {
382				if (fDefaultPrinter) {
383					// the printer exists, but is not the default printer
384					if (strcmp(fDefaultPrinter->Name(), printerName) != 0)
385						status = B_OK;
386					return status;
387				}
388				// the printer exists, but no default at all
389				return B_OK;
390			} else {
391				info.SetTo(B_TRANSLATE(
392					"A printer with that name already exists, "
393					"but its driver could not be found! Replace?"));
394			}
395		} else {
396			info.SetTo(B_TRANSLATE(
397				"A printer with that name already exists, "
398				"but it's not usable at all! Replace?"));
399		}
400
401		if (info.Length() != 0) {
402			BAlert *alert = new BAlert("Info", info.String(),
403				B_TRANSLATE("Cancel"), B_TRANSLATE("OK"));
404			alert->SetShortcut(0, B_ESCAPE);
405			if (alert->Go() == 0)
406				return status;
407		}
408	} else if (status != B_OK) {
409		return status;
410	}
411
412	// Set its type to a printer
413	BNodeInfo info(&printer);
414	info.SetType(PSRV_PRINTER_FILETYPE);
415
416	// Store the settings in its attributes
417	printer.WriteAttr(PSRV_PRINTER_ATTR_PRT_NAME, B_STRING_TYPE, 0, printerName,
418		::strlen(printerName) + 1);
419	printer.WriteAttr(PSRV_PRINTER_ATTR_DRV_NAME, B_STRING_TYPE, 0, driverName,
420		::strlen(driverName) + 1);
421	printer.WriteAttr(PSRV_PRINTER_ATTR_TRANSPORT, B_STRING_TYPE, 0,
422		transportName, ::strlen(transportName) + 1);
423	printer.WriteAttr(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, B_STRING_TYPE, 0,
424		transportPath, ::strlen(transportPath) + 1);
425	printer.WriteAttr(PSRV_PRINTER_ATTR_CNX, B_STRING_TYPE, 0, connection,
426		::strlen(connection) + 1);
427
428	status = Printer::ConfigurePrinter(driverName, printerName);
429	if (status == B_OK) {
430		// Notify printer driver that a new printer definition node
431		// has been created.
432		printer.WriteAttr(PSRV_PRINTER_ATTR_STATE, B_STRING_TYPE, 0, "free",
433			::strlen("free")+1);
434	}
435
436	if (status != B_OK) {
437		BEntry entry;
438		if (printer.GetEntry(&entry) == B_OK)
439			entry.Remove();
440	}
441
442	return status;
443}
444
445
446/*!	Makes a new printer the active printer. This is done simply
447	by changing our class attribute fDefaultPrinter, and changing
448	the icon of the BNode for the printer. Ofcourse, we need to
449	change the icon of the "old" default printer first back to a
450	"non-active" printer icon first.
451
452	@param printerName Name of the new active printer.
453	@return B_OK on success, or error code otherwise.
454*/
455status_t
456PrintServerApp::SelectPrinter(const char* printerName)
457{
458	// Find the node of the "old" default printer
459	BNode node;
460	if (fDefaultPrinter != NULL
461		&& FindPrinterNode(fDefaultPrinter->Name(), node) == B_OK) {
462		// and remove the custom icon
463		BNodeInfo info(&node);
464		info.SetIcon(NULL, B_MINI_ICON);
465		info.SetIcon(NULL, B_LARGE_ICON);
466	}
467
468	// Find the node for the new default printer
469	status_t status = FindPrinterNode(printerName, node);
470	if (status == B_OK) {
471		// and add the custom icon
472		BNodeInfo info(&node);
473		info.SetIcon(fSelectedIcon, fIconSize);
474	}
475
476	fDefaultPrinter = Printer::Find(printerName);
477	StoreDefaultPrinter();
478		// update our pref file
479	be_roster->Broadcast(new BMessage(B_PRINTER_CHANGED));
480
481	return status;
482}
483
484
485//! Handles calling the printer drivers for printing a spooled job.
486void
487PrintServerApp::HandleSpooledJobs()
488{
489	const int n = Printer::CountPrinters();
490	for (int i = 0; i < n; i ++) {
491		Printer* printer = Printer::At(i);
492		printer->HandleSpooledJob();
493	}
494}
495
496
497/*!	Loads the currently selected printer from a private settings
498	file.
499
500	@return Error code on failore, or B_OK if all went fine.
501*/
502status_t
503PrintServerApp::RetrieveDefaultPrinter()
504{
505	fDefaultPrinter = Printer::Find(fSettings->DefaultPrinter());
506	return B_OK;
507}
508
509
510/*!	Stores the currently selected printer in a private settings
511	file.
512
513	@return Error code on failore, or B_OK if all went fine.
514*/
515status_t
516PrintServerApp::StoreDefaultPrinter()
517{
518	if (fDefaultPrinter)
519		fSettings->SetDefaultPrinter(fDefaultPrinter->Name());
520	else
521		fSettings->SetDefaultPrinter("");
522	return B_OK;
523}
524
525
526/*!	Find the BNode representing the specified printer. It searches
527	*only* in the users printer definitions.
528
529	@param name Name of the printer to look for.
530	@param node BNode to set to the printer definition node.
531	@return B_OK if found, an error code otherwise.
532*/
533status_t
534PrintServerApp::FindPrinterNode(const char* name, BNode& node)
535{
536	// Find directory containing printer definitions
537	BPath path;
538	status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path, true,
539		NULL);
540	if (status != B_OK)
541		return status;
542
543	path.Append(name);
544	return node.SetTo(path.Path());
545}
546
547
548bool
549PrintServerApp::OpenSettings(BFile& file, const char* name, bool forReading)
550{
551	BPath path;
552	uint32 openMode = forReading ? B_READ_ONLY : B_CREATE_FILE | B_ERASE_FILE
553		| B_WRITE_ONLY;
554	return find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK
555		&& path.Append(name) == B_OK
556		&& file.SetTo(path.Path(), openMode) == B_OK;
557}
558
559
560void
561PrintServerApp::LoadSettings()
562{
563	BFile file;
564	if (OpenSettings(file, kSettingsName, true)) {
565		fSettings->Load(&file);
566		fUseConfigWindow = fSettings->UseConfigWindow();
567	}
568}
569
570
571void
572PrintServerApp::SaveSettings()
573{
574	BFile file;
575	if (OpenSettings(file, kSettingsName, false)) {
576		fSettings->SetUseConfigWindow(fUseConfigWindow);
577		fSettings->Save(&file);
578	}
579}
580