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