xref: /haiku/src/servers/print/Printer.cpp (revision 7120e97489acbf17d86d3f33e3b2e68974fd4b23)
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 
49 	// BeOS API
50 #include <Application.h>
51 #include <Autolock.h>
52 #include <Message.h>
53 #include <NodeMonitor.h>
54 #include <String.h>
55 #include <StorageKit.h>
56 #include <SupportDefs.h>
57 
58 
59 SpoolFolder::SpoolFolder(BLocker*locker, BLooper* looper, const BDirectory& spoolDir)
60 	: Folder(locker, looper, spoolDir)
61 {
62 }
63 
64 
65 // Notify print_server that there is a job file waiting for printing
66 void SpoolFolder::Notify(Job* job, int kind)
67 {
68 	if ((kind == kJobAdded || kind == kJobAttrChanged) &&
69 		job->IsValid() && job->IsWaiting()) {
70 		be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB);
71 	}
72 }
73 
74 
75 // ---------------------------------------------------------------
76 typedef BMessage* (*config_func_t)(BNode*, const BMessage*);
77 typedef BMessage* (*take_job_func_t)(BFile*, BNode*, const BMessage*);
78 typedef char* (*add_printer_func_t)(const char* printer_name);
79 typedef BMessage* (*default_settings_t)(BNode*);
80 
81 // ---------------------------------------------------------------
82 BObjectList<Printer> Printer::sPrinters;
83 
84 
85 // ---------------------------------------------------------------
86 // Find [static]
87 //
88 // Searches the static object list for a printer object with the
89 // specified name.
90 //
91 // Parameters:
92 //    name - Printer definition name we're looking for.
93 //
94 // Returns:
95 //    Pointer to Printer object, or NULL if not found.
96 // ---------------------------------------------------------------
97 Printer* Printer::Find(const BString& name)
98 {
99 		// Look in list to find printer definition
100 	for (int32 idx=0; idx < sPrinters.CountItems(); idx++) {
101 		if (name == sPrinters.ItemAt(idx)->Name()) {
102 			return sPrinters.ItemAt(idx);
103 		}
104 	}
105 
106 		// None found, so return NULL
107 	return NULL;
108 }
109 
110 Printer* Printer::Find(node_ref* node)
111 {
112 	node_ref n;
113 		// Look in list to find printer definition
114 	for (int32 idx=0; idx < sPrinters.CountItems(); idx++) {
115 		Printer* printer = sPrinters.ItemAt(idx);
116 		printer->SpoolDir()->GetNodeRef(&n);
117 		if (n == *node) return printer;
118 	}
119 
120 		// None found, so return NULL
121 	return NULL;
122 }
123 
124 Printer* Printer::At(int32 idx)
125 {
126 	return sPrinters.ItemAt(idx);
127 }
128 
129 void Printer::Remove(Printer* printer) {
130 	sPrinters.RemoveItem(printer);
131 }
132 
133 int32 Printer::CountPrinters()
134 {
135 	return sPrinters.CountItems();
136 }
137 
138 // ---------------------------------------------------------------
139 // Printer [constructor]
140 //
141 // Initializes the printer object with data read from the
142 // attributes attached to the printer definition node.
143 //
144 // Parameters:
145 //    node - Printer definition node for this printer.
146 //
147 // Returns:
148 //    none.
149 // ---------------------------------------------------------------
150 Printer::Printer(const BDirectory* node, Resource* res)
151 	: Inherited(B_EMPTY_STRING),
152 	fPrinter(gLock, be_app, *node),
153 	fResource(res),
154 	fSinglePrintThread(true),
155 	fJob(NULL),
156 	fProcessing(0),
157 	fAbort(false)
158 {
159 	BString name;
160 		// Set our name to the name of the passed node
161 	if (SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_PRT_NAME, &name) == B_OK)
162 		SetName(name.String());
163 
164 	if (name == "Preview") fSinglePrintThread = false;
165 
166 		// Add us to the global list of known printer definitions
167 	sPrinters.AddItem(this);
168 }
169 
170 Printer::~Printer()
171 {
172 	((PrintServerApp*)be_app)->NotifyPrinterDeletion(this);
173 }
174 
175 
176 // Remove printer spooler directory
177 status_t Printer::Remove()
178 {
179 	status_t rc = B_OK;
180 	BPath path;
181 
182 	if ((rc=::find_directory(B_USER_PRINTERS_DIRECTORY, &path)) == B_OK) {
183 		path.Append(Name());
184 		rc = rmdir(path.Path());
185 	}
186 
187 	return rc;
188 }
189 
190 // ---------------------------------------------------------------
191 // ConfigurePrinter
192 //
193 // Handles calling the printer addon's add_printer function.
194 //
195 // Parameters:
196 //    none.
197 //
198 // Returns:
199 //    B_OK if successful or errorcode otherwise.
200 // ---------------------------------------------------------------
201 status_t Printer::ConfigurePrinter()
202 {
203 	image_id id;
204 	status_t rc;
205 
206 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
207 			// Addon was loaded, so try and get the add_printer symbol
208 		add_printer_func_t func;
209 
210 		if (get_image_symbol(id, "add_printer", B_SYMBOL_TYPE_TEXT, (void**)&func) == B_OK) {
211 				// call the function and check its result
212 			rc = ((*func)(Name()) == NULL) ? B_ERROR : B_OK;
213 		}
214 
215 		::unload_add_on(id);
216 	}
217 
218 	return rc;
219 }
220 
221 // ---------------------------------------------------------------
222 // ConfigurePage
223 //
224 // Handles calling the printer addon's config_page function.
225 //
226 // Parameters:
227 //    settings - Page settings to display. The contents of this
228 //               message will be replaced with the new settings
229 //               if the function returns success.
230 //
231 // Returns:
232 //    B_OK if successful or errorcode otherwise.
233 // ---------------------------------------------------------------
234 status_t Printer::ConfigurePage(BMessage& settings)
235 {
236 	image_id id;
237 	status_t rc;
238 
239 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
240 			// Addon was loaded, so try and get the config_page symbol
241 		config_func_t func;
242 
243 		if ((rc=get_image_symbol(id, "config_page", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
244 				// call the function and check its result
245 			BMessage* new_settings = (*func)(SpoolDir(), &settings);
246 			if (new_settings != NULL && new_settings->what != 'baad') {
247 				settings = *new_settings;
248 				AddCurrentPrinter(&settings);
249 			} else
250 				rc = B_ERROR;
251 			delete new_settings;
252 		}
253 
254 		::unload_add_on(id);
255 	}
256 
257 	return rc;
258 }
259 
260 // ---------------------------------------------------------------
261 // ConfigureJob
262 //
263 // Handles calling the printer addon's config_job function.
264 //
265 // Parameters:
266 //    settings - Job settings to display. The contents of this
267 //               message will be replaced with the new settings
268 //               if the function returns success.
269 //
270 // Returns:
271 //    B_OK if successful or errorcode otherwise.
272 // ---------------------------------------------------------------
273 status_t Printer::ConfigureJob(BMessage& settings)
274 {
275 	image_id id;
276 	status_t rc;
277 
278 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
279 			// Addon was loaded, so try and get the config_job symbol
280 		config_func_t func;
281 
282 		if ((rc=get_image_symbol(id, "config_job", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
283 				// call the function and check its result
284 			BMessage* new_settings = (*func)(SpoolDir(), &settings);
285 			if ((new_settings != NULL) && (new_settings->what != 'baad')) {
286 				settings = *new_settings;
287 				AddCurrentPrinter(&settings);
288 			} else
289 				rc = B_ERROR;
290 			delete new_settings;
291 		}
292 
293 		::unload_add_on(id);
294 	}
295 
296 	return rc;
297 }
298 
299 
300 // ---------------------------------------------------------------
301 // HandleSpooledJobs
302 //
303 // Print spooled jobs in a new thread.
304 // ---------------------------------------------------------------
305 void Printer::HandleSpooledJob() {
306 	BAutolock lock(gLock);
307 	if (lock.IsLocked() && (!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 	image_id id;
326 	status_t rc;
327 
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 
332 		if ((rc=get_image_symbol(id, "default_settings", B_SYMBOL_TYPE_TEXT, (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 				AddCurrentPrinter(&settings);
338 			} else {
339 				rc = B_ERROR;
340 			}
341 			delete new_settings;
342 		}
343 
344 		::unload_add_on(id);
345 	}
346 
347 	return rc;
348 }
349 
350 
351 void Printer::AbortPrintThread() {
352 	fAbort = true;
353 }
354 
355 // ---------------------------------------------------------------
356 // LoadPrinterAddon
357 //
358 // Try to load the printer addon into memory.
359 //
360 // Parameters:
361 //    id - image_id set to the image id of the loaded addon.
362 //
363 // Returns:
364 //    B_OK if successful or errorcode otherwise.
365 // ---------------------------------------------------------------
366 status_t Printer::LoadPrinterAddon(image_id& id)
367 {
368 	BString drName;
369 	status_t rc;
370 	BPath path;
371 
372 	if ((rc=SpoolDir()->ReadAttrString(PSRV_PRINTER_ATTR_DRV_NAME, &drName)) == B_OK) {
373 			// try to locate the driver
374 		if ((rc=::TestForAddonExistence(drName.String(), B_USER_ADDONS_DIRECTORY, "Print", path)) != B_OK) {
375 			if ((rc=::TestForAddonExistence(drName.String(), B_COMMON_ADDONS_DIRECTORY, "Print", path)) != B_OK) {
376 				rc = ::TestForAddonExistence(drName.String(), B_BEOS_ADDONS_DIRECTORY, "Print", path);
377 			}
378 		}
379 
380 			// If the driver was found
381 		if (rc == B_OK) {
382 				// If we cannot load the addon
383 			if ((id=::load_add_on(path.Path())) < 0)
384 				rc = id;
385 		}
386 	}
387 	return rc;
388 }
389 
390 void Printer::AddCurrentPrinter(BMessage* msg)
391 {
392 	BString name;
393 	GetName(name);
394 	if (msg->HasString(PSRV_FIELD_CURRENT_PRINTER)) {
395 		msg->ReplaceString(PSRV_FIELD_CURRENT_PRINTER, name.String());
396 	} else {
397 		msg->AddString(PSRV_FIELD_CURRENT_PRINTER, name.String());
398 	}
399 }
400 
401 void Printer::MessageReceived(BMessage* msg)
402 {
403 	switch(msg->what) {
404 		case B_GET_PROPERTY:
405 		case B_SET_PROPERTY:
406 		case B_CREATE_PROPERTY:
407 		case B_DELETE_PROPERTY:
408 		case B_COUNT_PROPERTIES:
409 		case B_EXECUTE_PROPERTY:
410 			HandleScriptingCommand(msg);
411 			break;
412 
413 		default:
414 			Inherited::MessageReceived(msg);
415 	}
416 }
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 bool Printer::HasCurrentPrinter(BString& name) {
424 	BMessage settings;
425 		// read settings from spool file and get printer name
426 	BFile jobFile(&fJob->EntryRef(), B_READ_WRITE);
427 	return jobFile.InitCheck() == B_OK &&
428 		jobFile.Seek(sizeof(print_file_header), SEEK_SET) == sizeof(print_file_header) &&
429 		settings.Unflatten(&jobFile) == B_OK &&
430 		settings.FindString(PSRV_FIELD_CURRENT_PRINTER, &name) == B_OK;
431 }
432 
433 bool Printer::MoveJob(const BString& name) {
434 	BPath file(&fJob->EntryRef());
435 	BPath path;
436 	file.GetParent(&path);
437 	path.Append(".."); path.Append(name.String());
438 	BDirectory dir(path.Path());
439 	BEntry entry(&fJob->EntryRef());
440 		// try to move job file to proper directory
441 	return entry.MoveTo(&dir) == B_OK;
442 }
443 
444 bool Printer::FindSpooledJob() {
445 	BString name2;
446 	GetName(name2);
447 	do {
448 		fJob = fPrinter.GetNextJob();
449 		if (fJob) {
450 			BString name;
451 			if (HasCurrentPrinter(name) && name != name2 && MoveJob(name)) {
452 				fJob->SetStatus(kUnknown, false); // so that fPrinter.GetNextJob skips it
453 				fJob->Release();
454 			} else {
455 				fJob->SetPrinter(this);
456 				return true;
457 			}
458 		}
459 	} while (fJob != NULL);
460 	return false;
461 }
462 
463 status_t Printer::PrintSpooledJob(BFile* spoolFile)
464 {
465 	take_job_func_t func;
466 	image_id id;
467 	status_t rc;
468 
469 	if ((rc=LoadPrinterAddon(id)) == B_OK) {
470 			// Addon was loaded, so try and get the take_job symbol
471 		if ((rc=get_image_symbol(id, "take_job", B_SYMBOL_TYPE_TEXT, (void**)&func)) == B_OK) {
472 				// This seems to be required for legacy?
473 				// HP PCL3 add-on crashes without it!
474 			BMessage params('_RRC');
475 			params.AddInt32("file", (int32)spoolFile);
476 			params.AddInt32("printer", (int32)SpoolDir());
477 				// call the function and check its result
478 			BMessage* result = (*func)(spoolFile, SpoolDir(), &params);
479 
480 			if (result == NULL || result->what != 'okok')
481 				rc = B_ERROR;
482 
483 			delete result;
484 		}
485 
486 		::unload_add_on(id);
487 	}
488 
489 	return rc;
490 }
491 
492 
493 void Printer::PrintThread(Job* job) {
494 		// Wait until resource is available
495 	fResource->Lock();
496 	bool failed = true;
497 		// Can we continue?
498 	if (!fAbort) {
499 		BFile jobFile(&job->EntryRef(), B_READ_WRITE);
500 				// Tell the printer to print the spooled job
501 		if (jobFile.InitCheck() == B_OK && PrintSpooledJob(&jobFile) == B_OK) {
502 				// Remove spool file if printing was successfull.
503 			job->Remove(); failed = false;
504 		}
505 	}
506 		// Set status of spooled job on error
507 	if (failed) job->SetStatus(kFailed);
508 	fResource->Unlock();
509 	job->Release();
510 	atomic_add(&fProcessing, -1);
511 	Release();
512 		// Notify print_server to process next spooled job
513 	be_app_messenger.SendMessage(PSRV_PRINT_SPOOLED_JOB);
514 }
515 
516 status_t Printer::print_thread(void* data) {
517 	Job* job = static_cast<Job*>(data);
518 	job->GetPrinter()->PrintThread(job);
519 	return 0;
520 }
521 
522 void Printer::StartPrintThread() {
523 	Acquire();
524 	thread_id tid = spawn_thread(print_thread, "print", B_NORMAL_PRIORITY, (void*)fJob);
525 	if (tid > 0) {
526 		fJob->SetStatus(kProcessing);
527 		atomic_add(&fProcessing, 1);
528 		resume_thread(tid);
529 	} else {
530 		fJob->Release(); Release();
531 	}
532 }
533 
534