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