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