xref: /haiku/src/servers/print/Printer.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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 "Printer.h"
10 
11 #include "pr_server.h"
12 #include "BeUtils.h"
13 #include "PrintServerApp.h"
14 
15 	// posix
16 #include <limits.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20 
21 	// BeOS API
22 #include <Application.h>
23 #include <Autolock.h>
24 #include <Message.h>
25 #include <NodeMonitor.h>
26 #include <String.h>
27 #include <StorageKit.h>
28 #include <SupportDefs.h>
29 
30 
31 SpoolFolder::SpoolFolder(BLocker*locker, BLooper* looper, const BDirectory& spoolDir)
32 	: Folder(locker, looper, spoolDir)
33 {
34 }
35 
36 
37 // Notify print_server that there is a job file waiting for printing
38 void SpoolFolder::Notify(Job* job, int kind)
39 {
40 	if ((kind == kJobAdded || kind == kJobAttrChanged)
41 		&& job->IsValid() && job->IsWaiting()) {
42 		be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB);
43 	}
44 }
45 
46 
47 // ---------------------------------------------------------------
48 typedef BMessage* (*config_func_t)(BNode*, const BMessage*);
49 typedef BMessage* (*take_job_func_t)(BFile*, BNode*, const BMessage*);
50 typedef char* (*add_printer_func_t)(const char* printer_name);
51 typedef BMessage* (*default_settings_t)(BNode*);
52 
53 // ---------------------------------------------------------------
54 BObjectList<Printer> Printer::sPrinters;
55 
56 
57 // ---------------------------------------------------------------
58 // Find [static]
59 //
60 // Searches the static object list for a printer object with the
61 // specified name.
62 //
63 // Parameters:
64 //    name - Printer definition name we're looking for.
65 //
66 // Returns:
67 //    Pointer to Printer object, or NULL if not found.
68 // ---------------------------------------------------------------
69 Printer* Printer::Find(const BString& name)
70 {
71 		// Look in list to find printer definition
72 	for (int32 idx=0; idx < sPrinters.CountItems(); idx++) {
73 		if (name == sPrinters.ItemAt(idx)->Name())
74 			return sPrinters.ItemAt(idx);
75 	}
76 	return NULL;
77 }
78 
79 
80 Printer* Printer::Find(node_ref* node)
81 {
82 	node_ref n;
83 		// Look in list to find printer definition
84 	for (int32 idx = 0; idx < sPrinters.CountItems(); idx++) {
85 		Printer* printer = sPrinters.ItemAt(idx);
86 		printer->SpoolDir()->GetNodeRef(&n);
87 		if (n == *node)
88 			return printer;
89 	}
90 
91 		// None found, so return NULL
92 	return NULL;
93 }
94 
95 
96 Printer* Printer::At(int32 idx)
97 {
98 	return sPrinters.ItemAt(idx);
99 }
100 
101 
102 void Printer::Remove(Printer* printer)
103 {
104 	sPrinters.RemoveItem(printer);
105 }
106 
107 int32 Printer::CountPrinters()
108 {
109 	return sPrinters.CountItems();
110 }
111 
112 
113 // ---------------------------------------------------------------
114 // Printer [constructor]
115 //
116 // Initializes the printer object with data read from the
117 // attributes attached to the printer definition node.
118 //
119 // Parameters:
120 //    node - Printer definition node for this printer.
121 //
122 // Returns:
123 //    none.
124 // ---------------------------------------------------------------
125 Printer::Printer(const BDirectory* node, Resource* res)
126 	: Inherited(B_EMPTY_STRING),
127 	fPrinter(gLock, be_app, *node),
128 	fResource(res),
129 	fSinglePrintThread(res->NeedsLocking()),
130 	fJob(NULL),
131 	fProcessing(0),
132 	fAbort(false)
133 {
134 	BString name;
135 		// Set our name to the name of the passed node
136 	if (SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_PRT_NAME, &name) == B_OK)
137 		SetName(name.String());
138 
139 		// Add us to the global list of known printer definitions
140 	sPrinters.AddItem(this);
141 
142 	ResetJobStatus();
143 }
144 
145 
146 Printer::~Printer()
147 {
148 	((PrintServerApp*)be_app)->NotifyPrinterDeletion(this);
149 }
150 
151 
152 void Printer::MessageReceived(BMessage* msg)
153 {
154 	switch(msg->what) {
155 		case B_GET_PROPERTY:
156 		case B_SET_PROPERTY:
157 		case B_CREATE_PROPERTY:
158 		case B_DELETE_PROPERTY:
159 		case B_COUNT_PROPERTIES:
160 		case B_EXECUTE_PROPERTY:
161 			HandleScriptingCommand(msg);
162 			break;
163 
164 		default:
165 			Inherited::MessageReceived(msg);
166 	}
167 }
168 
169 
170 // Remove printer spooler directory
171 status_t Printer::Remove()
172 {
173 	status_t rc = B_OK;
174 	BPath path;
175 
176 	if ((rc=::find_directory(B_USER_PRINTERS_DIRECTORY, &path)) == B_OK) {
177 		path.Append(Name());
178 		rc = rmdir(path.Path());
179 	}
180 
181 	return rc;
182 }
183 
184 
185 // ---------------------------------------------------------------
186 // ConfigurePrinter
187 //
188 // Handles calling the printer addon's add_printer function.
189 //
190 // Parameters:
191 //    none.
192 //
193 // Returns:
194 //    B_OK if successful or errorcode otherwise.
195 // ---------------------------------------------------------------
196 status_t Printer::ConfigurePrinter()
197 {
198 	status_t rc;
199 	image_id id;
200 	if ((rc = LoadPrinterAddon(id)) == B_OK) {
201 			// Addon was loaded, so try and get the add_printer symbol
202 		add_printer_func_t func;
203 		if (get_image_symbol(id, "add_printer", B_SYMBOL_TYPE_TEXT,
204 			(void**)&func) == B_OK) {
205 			// call the function and check its result
206 			rc = ((*func)(Name()) == NULL) ? B_ERROR : B_OK;
207 		}
208 
209 		::unload_add_on(id);
210 	}
211 
212 	return rc;
213 }
214 
215 
216 // ---------------------------------------------------------------
217 // ConfigurePage
218 //
219 // Handles calling the printer addon's config_page function.
220 //
221 // Parameters:
222 //    settings - Page settings to display. The contents of this
223 //               message will be replaced with the new settings
224 //               if the function returns success.
225 //
226 // Returns:
227 //    B_OK if successful or errorcode otherwise.
228 // ---------------------------------------------------------------
229 status_t Printer::ConfigurePage(BMessage& settings)
230 {
231 	status_t rc;
232 	image_id id;
233 	if ((rc = LoadPrinterAddon(id)) == B_OK) {
234 			// Addon was loaded, so try and get the config_page symbol
235 		config_func_t func;
236 		if ((rc=get_image_symbol(id, "config_page", B_SYMBOL_TYPE_TEXT,
237 			(void**)&func)) == B_OK) {
238 			// call the function and check its result
239 			BMessage* new_settings = (*func)(SpoolDir(), &settings);
240 			if (new_settings != NULL && new_settings->what != 'baad') {
241 				settings = *new_settings;
242 				settings.what = 'okok';
243 				AddCurrentPrinter(settings);
244 			} else {
245 				rc = B_ERROR;
246 			}
247 			delete new_settings;
248 		}
249 
250 		::unload_add_on(id);
251 	}
252 
253 	return rc;
254 }
255 
256 
257 // ---------------------------------------------------------------
258 // ConfigureJob
259 //
260 // Handles calling the printer addon's config_job function.
261 //
262 // Parameters:
263 //    settings - Job settings to display. The contents of this
264 //               message will be replaced with the new settings
265 //               if the function returns success.
266 //
267 // Returns:
268 //    B_OK if successful or errorcode otherwise.
269 // ---------------------------------------------------------------
270 status_t Printer::ConfigureJob(BMessage& settings)
271 {
272 	status_t rc;
273 	image_id id;
274 	if ((rc = LoadPrinterAddon(id)) == B_OK) {
275 			// Addon was loaded, so try and get the config_job symbol
276 		config_func_t func;
277 		if ((rc = get_image_symbol(id, "config_job", B_SYMBOL_TYPE_TEXT,
278 			(void**)&func)) == B_OK) {
279 			// call the function and check its result
280 			BMessage* new_settings = (*func)(SpoolDir(), &settings);
281 			if ((new_settings != NULL) && (new_settings->what != 'baad')) {
282 				settings = *new_settings;
283 				settings.what = 'okok';
284 				AddCurrentPrinter(settings);
285 			} else {
286 				rc = B_ERROR;
287 			}
288 			delete new_settings;
289 		}
290 
291 		::unload_add_on(id);
292 	}
293 
294 	return rc;
295 }
296 
297 
298 // ---------------------------------------------------------------
299 // HandleSpooledJobs
300 //
301 // Print spooled jobs in a new thread.
302 // ---------------------------------------------------------------
303 void Printer::HandleSpooledJob()
304 {
305 	BAutolock lock(gLock);
306 	if (lock.IsLocked()
307 		&& (!fSinglePrintThread || fProcessing == 0) && FindSpooledJob()) {
308 		StartPrintThread();
309 	}
310 }
311 
312 
313 // ---------------------------------------------------------------
314 // GetDefaultSettings
315 //
316 // Retrieve the default configuration message from printer add-on
317 //
318 // Parameters:
319 //   settings, output paramter.
320 //
321 // Returns:
322 //    B_OK if successful or errorcode otherwise.
323 // ---------------------------------------------------------------
324 status_t Printer::GetDefaultSettings(BMessage& settings)
325 {
326 	status_t rc;
327 	image_id id;
328 	if ((rc = LoadPrinterAddon(id)) == B_OK) {
329 			// Addon was loaded, so try and get the default_settings symbol
330 		default_settings_t func;
331 		if ((rc = get_image_symbol(id, "default_settings", B_SYMBOL_TYPE_TEXT,
332 			(void**)&func)) == B_OK) {
333 				// call the function and check its result
334 			BMessage* new_settings = (*func)(SpoolDir());
335 			if (new_settings) {
336 				settings = *new_settings;
337 				settings.what = 'okok';
338 				AddCurrentPrinter(settings);
339 			} else {
340 				rc = B_ERROR;
341 			}
342 			delete new_settings;
343 		}
344 
345 		::unload_add_on(id);
346 	}
347 	return rc;
348 }
349 
350 
351 void Printer::AbortPrintThread()
352 {
353 	fAbort = true;
354 }
355 
356 
357 // ---------------------------------------------------------------
358 // LoadPrinterAddon
359 //
360 // Try to load the printer addon into memory.
361 //
362 // Parameters:
363 //    id - image_id set to the image id of the loaded addon.
364 //
365 // Returns:
366 //    B_OK if successful or errorcode otherwise.
367 // ---------------------------------------------------------------
368 status_t Printer::LoadPrinterAddon(image_id& id)
369 {
370 	status_t rc;
371 	BString drName;
372 	if ((rc = SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_DRV_NAME, &drName)) == B_OK) {
373 		// try to locate the driver
374 		BPath path;
375 		if ((rc= ::TestForAddonExistence(drName.String(), B_USER_ADDONS_DIRECTORY,
376 			"Print", path)) != B_OK) {
377 			if ((rc = ::TestForAddonExistence(drName.String(), B_COMMON_ADDONS_DIRECTORY,
378 				"Print", path)) != B_OK) {
379 				rc = ::TestForAddonExistence(drName.String(), B_BEOS_ADDONS_DIRECTORY,
380 					"Print", path);
381 			}
382 		}
383 
384 		// If the driver was found
385 		if (rc == B_OK) {
386 			// If we cannot load the addon
387 			if ((id=::load_add_on(path.Path())) < 0)
388 				rc = id;
389 		}
390 	}
391 	return rc;
392 }
393 
394 
395 // ---------------------------------------------------------------
396 // AddCurrentPrinter
397 //
398 // Add printer name to message.
399 //
400 // Parameters:
401 //    msg - message.
402 // ---------------------------------------------------------------
403 void Printer::AddCurrentPrinter(BMessage& message)
404 {
405 	BString name;
406 	GetName(name);
407 
408 	message.RemoveName(PSRV_FIELD_CURRENT_PRINTER);
409 	message.AddString(PSRV_FIELD_CURRENT_PRINTER, name.String());
410 }
411 
412 
413 // ---------------------------------------------------------------
414 // GetName
415 //
416 // Get the name from spool directory.
417 //
418 // Parameters:
419 //    name - the name of the printer.
420 // ---------------------------------------------------------------
421 void Printer::GetName(BString& name)
422 {
423 	if (SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_PRT_NAME, &name) != B_OK)
424 		name = "Unknown Printer";
425 }
426 
427 
428 // ---------------------------------------------------------------
429 // ResetJobStatus
430 //
431 // Reset status of "processing" jobs to "waiting" at print_server start.
432 // ---------------------------------------------------------------
433 void Printer::ResetJobStatus()
434 {
435 	if (fPrinter.Lock()) {
436 		const int32 n = fPrinter.CountJobs();
437 		for (int32 i = 0; i < n; i ++) {
438 			Job* job = fPrinter.JobAt(i);
439 			if (job->Status() == kProcessing)
440 				job->SetStatus(kWaiting);
441 		}
442 		fPrinter.Unlock();
443 	}
444 }
445 
446 
447 // ---------------------------------------------------------------
448 // HasCurrentPrinter
449 //
450 // Try to read the printer name from job file.
451 //
452 // Parameters:
453 //    name - the printer name.
454 //
455 // Returns:
456 //    true if successful.
457 // ---------------------------------------------------------------
458 bool Printer::HasCurrentPrinter(BString& name)
459 {
460 	BMessage settings;
461 		// read settings from spool file and get printer name
462 	BFile jobFile(&fJob->EntryRef(), B_READ_WRITE);
463 	return jobFile.InitCheck() == B_OK
464 		&& jobFile.Seek(sizeof(print_file_header), SEEK_SET) == sizeof(print_file_header)
465 		&& settings.Unflatten(&jobFile) == B_OK
466 		&& settings.FindString(PSRV_FIELD_CURRENT_PRINTER, &name) == B_OK;
467 }
468 
469 
470 // ---------------------------------------------------------------
471 // MoveJob
472 //
473 // Try to move job to another printer folder.
474 //
475 // Parameters:
476 //    name - the printer folder name.
477 //
478 // Returns:
479 //    true if successful.
480 // ---------------------------------------------------------------
481 bool Printer::MoveJob(const BString& name)
482 {
483 	BPath file(&fJob->EntryRef());
484 	BPath path;
485 	file.GetParent(&path);
486 	path.Append("..");
487 	path.Append(name.String());
488 	BDirectory dir(path.Path());
489 	BEntry entry(&fJob->EntryRef());
490 		// try to move job file to proper directory
491 	return entry.MoveTo(&dir) == B_OK;
492 }
493 
494 
495 // ---------------------------------------------------------------
496 // FindSpooledJob
497 //
498 // Looks if there is a job waiting to be processed and moves
499 // jobs to the proper printer folder.
500 //
501 // Note:
502 //       Our implementation of BPrintJob moves jobs to the
503 //       proper printer folder.
504 //
505 //
506 // Returns:
507 //    true if there is a job present in fJob.
508 // ---------------------------------------------------------------
509 bool Printer::FindSpooledJob()
510 {
511 	BString name2;
512 	GetName(name2);
513 	do {
514 		fJob = fPrinter.GetNextJob();
515 		if (fJob) {
516 			BString name;
517 			if (HasCurrentPrinter(name) && name != name2 && MoveJob(name)) {
518 					// job in wrong printer folder moved to apropriate one
519 				fJob->SetStatus(kUnknown, false); // so that fPrinter.GetNextJob skips it
520 				fJob->Release();
521 			} else {
522 					// job found
523 				fJob->SetPrinter(this);
524 				return true;
525 			}
526 		}
527 	} while (fJob != NULL);
528 	return false;
529 }
530 
531 
532 // ---------------------------------------------------------------
533 // PrintSpooledJob
534 //
535 // Loads the printer add-on and calls its take_job function with
536 // the spool file as argument.
537 //
538 // Parameters:
539 //    spoolFile - the spool file.
540 //
541 // Returns:
542 //    B_OK if successful.
543 // ---------------------------------------------------------------
544 status_t Printer::PrintSpooledJob(BFile* spoolFile)
545 {
546 	status_t rc;
547 	image_id id;
548 	if ((rc = LoadPrinterAddon(id)) == B_OK) {
549 		take_job_func_t func;
550 		// Addon was loaded, so try and get the take_job symbol
551 		if ((rc = get_image_symbol(id, "take_job", B_SYMBOL_TYPE_TEXT,
552 			(void**)&func)) == B_OK) {
553 			// This seems to be required for legacy?
554 			// HP PCL3 add-on crashes without it!
555 			BMessage params(B_REFS_RECEIVED);
556 			params.AddInt32("file", (int32)spoolFile);
557 			params.AddInt32("printer", (int32)SpoolDir());
558 				// call the function and check its result
559 			BMessage* result = (*func)(spoolFile, SpoolDir(), &params);
560 
561 			if (result == NULL || result->what != 'okok')
562 				rc = B_ERROR;
563 			delete result;
564 		}
565 
566 		::unload_add_on(id);
567 	}
568 	return rc;
569 }
570 
571 
572 // ---------------------------------------------------------------
573 // PrintThread
574 //
575 // Loads the printer add-on and calls its take_job function with
576 // the spool file as argument.
577 //
578 // Parameters:
579 //    job - the spool job.
580 // ---------------------------------------------------------------
581 void Printer::PrintThread(Job* job)
582 {
583 	// Wait until resource is available
584 	fResource->Lock();
585 	bool failed = true;
586 		// Can we continue?
587 	if (!fAbort) {
588 		BFile jobFile(&job->EntryRef(), B_READ_WRITE);
589 				// Tell the printer to print the spooled job
590 		if (jobFile.InitCheck() == B_OK && PrintSpooledJob(&jobFile) == B_OK) {
591 				// Remove spool file if printing was successfull.
592 			job->Remove(); failed = false;
593 		}
594 	}
595 		// Set status of spooled job on error
596 	if (failed)
597 		job->SetStatus(kFailed);
598 	fResource->Unlock();
599 	job->Release();
600 	atomic_add(&fProcessing, -1);
601 	Release();
602 		// Notify print_server to process next spooled job
603 	be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB);
604 }
605 
606 
607 // ---------------------------------------------------------------
608 // print_thread
609 //
610 // Print thread entry, calls PrintThread with spool job.
611 //
612 // Parameters:
613 //    data - spool job.
614 //
615 // Returns:
616 //    0 always.
617 // ---------------------------------------------------------------
618 status_t Printer::print_thread(void* data)
619 {
620 	Job* job = static_cast<Job*>(data);
621 	job->GetPrinter()->PrintThread(job);
622 	return 0;
623 }
624 
625 
626 // ---------------------------------------------------------------
627 // StartPrintThread
628 //
629 // Sets the status of the current spool job to "processing" and
630 // starts the print_thread.
631 // ---------------------------------------------------------------
632 void Printer::StartPrintThread()
633 {
634 	Acquire();
635 	thread_id tid = spawn_thread(print_thread, "print", B_NORMAL_PRIORITY, (void*)fJob);
636 	if (tid > 0) {
637 		fJob->SetStatus(kProcessing);
638 		atomic_add(&fProcessing, 1);
639 		resume_thread(tid);
640 	} else {
641 		fJob->Release();
642 		Release();
643 	}
644 }
645 
646