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