xref: /haiku/src/servers/print/Printer.cpp (revision e5430a086c769ea76c3944046b1f07cf049c1ae0)
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 
171 Printer::~Printer()
172 {
173 	((PrintServerApp*)be_app)->NotifyPrinterDeletion(this);
174 }
175 
176 
177 // Remove printer spooler directory
178 status_t Printer::Remove()
179 {
180 	status_t rc = B_OK;
181 	BPath path;
182 
183 	if ((rc=::find_directory(B_USER_PRINTERS_DIRECTORY, &path)) == B_OK) {
184 		path.Append(Name());
185 		rc = rmdir(path.Path());
186 	}
187 
188 	return rc;
189 }
190 
191 // ---------------------------------------------------------------
192 // ConfigurePrinter
193 //
194 // Handles calling the printer addon's add_printer function.
195 //
196 // Parameters:
197 //    none.
198 //
199 // Returns:
200 //    B_OK if successful or errorcode otherwise.
201 // ---------------------------------------------------------------
202 status_t Printer::ConfigurePrinter()
203 {
204 	image_id id;
205 	status_t rc;
206 
207 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
208 			// Addon was loaded, so try and get the add_printer symbol
209 		add_printer_func_t func;
210 
211 		if (get_image_symbol(id, "add_printer", B_SYMBOL_TYPE_TEXT, (void**)&func) == B_OK) {
212 				// call the function and check its result
213 			rc = ((*func)(Name()) == NULL) ? B_ERROR : B_OK;
214 		}
215 
216 		::unload_add_on(id);
217 	}
218 
219 	return rc;
220 }
221 
222 // ---------------------------------------------------------------
223 // ConfigurePage
224 //
225 // Handles calling the printer addon's config_page function.
226 //
227 // Parameters:
228 //    settings - Page settings to display. The contents of this
229 //               message will be replaced with the new settings
230 //               if the function returns success.
231 //
232 // Returns:
233 //    B_OK if successful or errorcode otherwise.
234 // ---------------------------------------------------------------
235 status_t Printer::ConfigurePage(BMessage& settings)
236 {
237 	image_id id;
238 	status_t rc;
239 
240 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
241 			// Addon was loaded, so try and get the config_page symbol
242 		config_func_t func;
243 
244 		if ((rc=get_image_symbol(id, "config_page", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
245 				// call the function and check its result
246 			BMessage* new_settings = (*func)(SpoolDir(), &settings);
247 			if (new_settings != NULL && new_settings->what != 'baad') {
248 				settings = *new_settings;
249 				AddCurrentPrinter(&settings);
250 			} else
251 				rc = B_ERROR;
252 			delete new_settings;
253 		}
254 
255 		::unload_add_on(id);
256 	}
257 
258 	return rc;
259 }
260 
261 // ---------------------------------------------------------------
262 // ConfigureJob
263 //
264 // Handles calling the printer addon's config_job function.
265 //
266 // Parameters:
267 //    settings - Job settings to display. The contents of this
268 //               message will be replaced with the new settings
269 //               if the function returns success.
270 //
271 // Returns:
272 //    B_OK if successful or errorcode otherwise.
273 // ---------------------------------------------------------------
274 status_t Printer::ConfigureJob(BMessage& settings)
275 {
276 	image_id id;
277 	status_t rc;
278 
279 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
280 			// Addon was loaded, so try and get the config_job symbol
281 		config_func_t func;
282 
283 		if ((rc=get_image_symbol(id, "config_job", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
284 				// call the function and check its result
285 			BMessage* new_settings = (*func)(SpoolDir(), &settings);
286 			if ((new_settings != NULL) && (new_settings->what != 'baad')) {
287 				settings = *new_settings;
288 				AddCurrentPrinter(&settings);
289 			} else
290 				rc = B_ERROR;
291 			delete new_settings;
292 		}
293 
294 		::unload_add_on(id);
295 	}
296 
297 	return rc;
298 }
299 
300 
301 // ---------------------------------------------------------------
302 // HandleSpooledJobs
303 //
304 // Print spooled jobs in a new thread.
305 // ---------------------------------------------------------------
306 void Printer::HandleSpooledJob() {
307 	BAutolock lock(gLock);
308 	if (lock.IsLocked() && (!fSinglePrintThread || fProcessing == 0) && FindSpooledJob()) {
309 		StartPrintThread();
310 	}
311 }
312 
313 
314 // ---------------------------------------------------------------
315 // GetDefaultSettings
316 //
317 // Retrieve the default configuration message from printer add-on
318 //
319 // Parameters:
320 //   settings, output paramter.
321 //
322 // Returns:
323 //    B_OK if successful or errorcode otherwise.
324 // ---------------------------------------------------------------
325 status_t Printer::GetDefaultSettings(BMessage& settings) {
326 	image_id id;
327 	status_t rc;
328 
329 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
330 			// Addon was loaded, so try and get the default_settings symbol
331 		default_settings_t func;
332 
333 		if ((rc=get_image_symbol(id, "default_settings", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
334 				// call the function and check its result
335 			BMessage* new_settings = (*func)(SpoolDir());
336 			if (new_settings) {
337 				settings = *new_settings;
338 				AddCurrentPrinter(&settings);
339 			} else {
340 				rc = B_ERROR;
341 			}
342 			delete new_settings;
343 		}
344 
345 		::unload_add_on(id);
346 	}
347 
348 	return rc;
349 }
350 
351 
352 void Printer::AbortPrintThread() {
353 	fAbort = true;
354 }
355 
356 // ---------------------------------------------------------------
357 // LoadPrinterAddon
358 //
359 // Try to load the printer addon into memory.
360 //
361 // Parameters:
362 //    id - image_id set to the image id of the loaded addon.
363 //
364 // Returns:
365 //    B_OK if successful or errorcode otherwise.
366 // ---------------------------------------------------------------
367 status_t Printer::LoadPrinterAddon(image_id& id)
368 {
369 	BString drName;
370 	status_t rc;
371 	BPath path;
372 
373 	if ((rc=SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_DRV_NAME, &drName)) == B_OK) {
374 			// try to locate the driver
375 		if ((rc=::TestForAddonExistence(drName.String(), B_USER_ADDONS_DIRECTORY, "Print", path)) != B_OK) {
376 			if ((rc=::TestForAddonExistence(drName.String(), B_COMMON_ADDONS_DIRECTORY, "Print", path)) != B_OK) {
377 				rc = ::TestForAddonExistence(drName.String(), B_BEOS_ADDONS_DIRECTORY, "Print", path);
378 			}
379 		}
380 
381 			// If the driver was found
382 		if (rc == B_OK) {
383 				// If we cannot load the addon
384 			if ((id=::load_add_on(path.Path())) < 0)
385 				rc = id;
386 		}
387 	}
388 	return rc;
389 }
390 
391 void Printer::AddCurrentPrinter(BMessage* msg)
392 {
393 	BString name;
394 	GetName(name);
395 	if (msg->HasString(PSRV_FIELD_CURRENT_PRINTER)) {
396 		msg->ReplaceString(PSRV_FIELD_CURRENT_PRINTER, name.String());
397 	} else {
398 		msg->AddString(PSRV_FIELD_CURRENT_PRINTER, name.String());
399 	}
400 }
401 
402 void Printer::MessageReceived(BMessage* msg)
403 {
404 	switch(msg->what) {
405 		case B_GET_PROPERTY:
406 		case B_SET_PROPERTY:
407 		case B_CREATE_PROPERTY:
408 		case B_DELETE_PROPERTY:
409 		case B_COUNT_PROPERTIES:
410 		case B_EXECUTE_PROPERTY:
411 			HandleScriptingCommand(msg);
412 			break;
413 
414 		default:
415 			Inherited::MessageReceived(msg);
416 	}
417 }
418 
419 void Printer::GetName(BString& name) {
420 	if (B_OK != SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_PRT_NAME, &name))
421 		name = "Unknown Printer";
422 }
423 
424 bool Printer::HasCurrentPrinter(BString& name) {
425 	BMessage settings;
426 		// read settings from spool file and get printer name
427 	BFile jobFile(&fJob->EntryRef(), B_READ_WRITE);
428 	return jobFile.InitCheck() == B_OK &&
429 		jobFile.Seek(sizeof(print_file_header), SEEK_SET) == sizeof(print_file_header) &&
430 		settings.Unflatten(&jobFile) == B_OK &&
431 		settings.FindString(PSRV_FIELD_CURRENT_PRINTER, &name) == B_OK;
432 }
433 
434 bool Printer::MoveJob(const BString& name) {
435 	BPath file(&fJob->EntryRef());
436 	BPath path;
437 	file.GetParent(&path);
438 	path.Append(".."); path.Append(name.String());
439 	BDirectory dir(path.Path());
440 	BEntry entry(&fJob->EntryRef());
441 		// try to move job file to proper directory
442 	return entry.MoveTo(&dir) == B_OK;
443 }
444 
445 bool Printer::FindSpooledJob() {
446 	BString name2;
447 	GetName(name2);
448 	do {
449 		fJob = fPrinter.GetNextJob();
450 		if (fJob) {
451 			BString name;
452 			if (HasCurrentPrinter(name) && name != name2 && MoveJob(name)) {
453 				fJob->SetStatus(kUnknown, false); // so that fPrinter.GetNextJob skips it
454 				fJob->Release();
455 			} else {
456 				fJob->SetPrinter(this);
457 				return true;
458 			}
459 		}
460 	} while (fJob != NULL);
461 	return false;
462 }
463 
464 status_t Printer::PrintSpooledJob(BFile* spoolFile)
465 {
466 	take_job_func_t func;
467 	image_id id;
468 	status_t rc;
469 
470 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
471 			// Addon was loaded, so try and get the take_job symbol
472 		if ((rc=get_image_symbol(id, "take_job", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
473 				// This seems to be required for legacy?
474 				// HP PCL3 add-on crashes without it!
475 			BMessage params('_RRC');
476 			params.AddInt32("file", (int32)spoolFile);
477 			params.AddInt32("printer", (int32)SpoolDir());
478 				// call the function and check its result
479 			BMessage* result = (*func)(spoolFile, SpoolDir(), &params);
480 
481 			if (result == NULL || result->what != 'okok')
482 				rc = B_ERROR;
483 
484 			delete result;
485 		}
486 
487 		::unload_add_on(id);
488 	}
489 
490 	return rc;
491 }
492 
493 
494 void Printer::PrintThread(Job* job) {
495 		// Wait until resource is available
496 	fResource->Lock();
497 	bool failed = true;
498 		// Can we continue?
499 	if (!fAbort) {
500 		BFile jobFile(&job->EntryRef(), B_READ_WRITE);
501 				// Tell the printer to print the spooled job
502 		if (jobFile.InitCheck() == B_OK && PrintSpooledJob(&jobFile) == B_OK) {
503 				// Remove spool file if printing was successfull.
504 			job->Remove(); failed = false;
505 		}
506 	}
507 		// Set status of spooled job on error
508 	if (failed) job->SetStatus(kFailed);
509 	fResource->Unlock();
510 	job->Release();
511 	atomic_add(&fProcessing, -1);
512 	Release();
513 		// Notify print_server to process next spooled job
514 	be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB);
515 }
516 
517 status_t Printer::print_thread(void* data) {
518 	Job* job = static_cast<Job*>(data);
519 	job->GetPrinter()->PrintThread(job);
520 	return 0;
521 }
522 
523 void Printer::StartPrintThread() {
524 	Acquire();
525 	thread_id tid = spawn_thread(print_thread, "print", B_NORMAL_PRIORITY, (void*)fJob);
526 	if (tid > 0) {
527 		fJob->SetStatus(kProcessing);
528 		atomic_add(&fProcessing, 1);
529 		resume_thread(tid);
530 	} else {
531 		fJob->Release(); Release();
532 	}
533 }
534 
535