xref: /haiku/src/servers/print/PrintServerApp.cpp (revision 8a6724a0ee3803f1e9f487d8111bb3f6cb8d16db)
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 
42 typedef struct _printer_data {
43 	char defaultPrinterName[256];
44 } printer_data_t;
45 
46 
47 static const char* kSettingsName = "print_server_settings";
48 
49 BLocker *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 */
57 int
58 main()
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 */
80 PrintServerApp::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 
122 PrintServerApp::~PrintServerApp()
123 {
124 	SaveSettings();
125 	delete fSettings;
126 }
127 
128 
129 bool
130 PrintServerApp::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 
165 void
166 PrintServerApp::Acquire()
167 {
168 	if (atomic_add(&fReferences, 1) == 0)
169 		acquire_sem(fHasReferences);
170 }
171 
172 
173 void
174 PrintServerApp::Release()
175 {
176 	if (atomic_add(&fReferences, -1) == 1)
177 		release_sem(fHasReferences);
178 }
179 
180 
181 void
182 PrintServerApp::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 
212 void
213 PrintServerApp::UnregisterPrinter(Printer* printer)
214 {
215 	RemoveHandler(printer);
216 	Printer::Remove(printer);
217 	printer->Release();
218 }
219 
220 
221 void
222 PrintServerApp::NotifyPrinterDeletion(Printer* printer)
223 {
224 	BAutolock lock(gLock);
225 	if (lock.IsLocked()) {
226 		fResourceManager.Free(printer->GetResource());
227 		Release();
228 	}
229 }
230 
231 
232 void
233 PrintServerApp::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 
243 void
244 PrintServerApp::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 
255 void
256 PrintServerApp::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 */
270 status_t
271 PrintServerApp::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 */
315 void
316 PrintServerApp::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 */
356 status_t
357 PrintServerApp::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 */
455 status_t
456 PrintServerApp::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.
486 void
487 PrintServerApp::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 */
502 status_t
503 PrintServerApp::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 */
515 status_t
516 PrintServerApp::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 */
533 status_t
534 PrintServerApp::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 
548 bool
549 PrintServerApp::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 
560 void
561 PrintServerApp::LoadSettings()
562 {
563 	BFile file;
564 	if (OpenSettings(file, kSettingsName, true)) {
565 		fSettings->Load(&file);
566 		fUseConfigWindow = fSettings->UseConfigWindow();
567 	}
568 }
569 
570 
571 void
572 PrintServerApp::SaveSettings()
573 {
574 	BFile file;
575 	if (OpenSettings(file, kSettingsName, false)) {
576 		fSettings->SetUseConfigWindow(fUseConfigWindow);
577 		fSettings->Save(&file);
578 	}
579 }
580