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