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