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