1 /*
2 * Copyright 2001-2015, 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
10
11 #include "PrintServerApp.h"
12
13 #include <stdio.h>
14 #include <unistd.h>
15
16 #include <Alert.h>
17 #include <Autolock.h>
18 #include <Catalog.h>
19 #include <Directory.h>
20 #include <File.h>
21 #include <FindDirectory.h>
22 #include <image.h>
23 #include <Locale.h>
24 #include <Mime.h>
25 #include <NodeInfo.h>
26 #include <NodeMonitor.h>
27 #include <Path.h>
28 #include <Roster.h>
29 #include <PrintJob.h>
30 #include <String.h>
31
32 #include "BeUtils.h"
33 #include "Printer.h"
34 #include "pr_server.h"
35 #include "Transport.h"
36
37
38 #undef B_TRANSLATION_CONTEXT
39 #define B_TRANSLATION_CONTEXT "PrintServerApp"
40
41
42 typedef struct _printer_data {
43 char defaultPrinterName[256];
44 } printer_data_t;
45
46
47 static const char* kSettingsName = "print_server_settings";
48
49 BLocker *gLock = NULL;
50
51
52 /*! Main entry point of print_server.
53
54 @returns B_OK if application was started, or an errorcode if
55 the application failed to start.
56 */
57 int
main()58 main()
59 {
60 gLock = new BLocker();
61
62 status_t status = B_OK;
63 PrintServerApp printServer(&status);
64 if (status == B_OK)
65 printServer.Run();
66
67 delete gLock;
68 return status;
69 }
70
71
72 /*! Constructor for print_server's application class. Retrieves the
73 name of the default printer from storage, caches the icons for
74 a selected printer.
75
76 @param err Pointer to status_t for storing result of application
77 initialisation.
78 @see BApplication
79 */
PrintServerApp(status_t * err)80 PrintServerApp::PrintServerApp(status_t* err)
81 :
82 Inherited(PSRV_SIGNATURE_TYPE, true, err),
83 fDefaultPrinter(NULL),
84 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
85 fIconSize(0),
86 fSelectedIcon(NULL),
87 #else
88 fSelectedIconMini(NULL),
89 fSelectedIconLarge(NULL),
90 #endif
91 fReferences(0),
92 fHasReferences(0),
93 fUseConfigWindow(true),
94 fFolder(NULL)
95 {
96 fSettings = Settings::GetSettings();
97 LoadSettings();
98
99 if (*err != B_OK)
100 return;
101
102 fHasReferences = create_sem(1, "has_references");
103
104 // Build list of transport addons
105 Transport::Scan(B_USER_NONPACKAGED_ADDONS_DIRECTORY);
106 Transport::Scan(B_USER_ADDONS_DIRECTORY);
107 Transport::Scan(B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY);
108 Transport::Scan(B_SYSTEM_ADDONS_DIRECTORY);
109
110 SetupPrinterList();
111 RetrieveDefaultPrinter();
112
113 // Cache icons for selected printer
114 BMimeType type(PSRV_PRINTER_FILETYPE);
115 type.GetIcon(&fSelectedIcon, &fIconSize);
116
117 PostMessage(PSRV_PRINT_SPOOLED_JOB);
118 // Start handling of spooled files
119 }
120
121
~PrintServerApp()122 PrintServerApp::~PrintServerApp()
123 {
124 SaveSettings();
125 delete fSettings;
126 }
127
128
129 bool
QuitRequested()130 PrintServerApp::QuitRequested()
131 {
132 // don't quit when user types Command+Q!
133 BMessage* m = CurrentMessage();
134 bool shortcut;
135 if (m != NULL && m->FindBool("shortcut", &shortcut) == B_OK && shortcut)
136 return false;
137
138 if (!Inherited::QuitRequested())
139 return false;
140
141 // Stop watching the folder
142 delete fFolder; fFolder = NULL;
143
144 // Release all printers
145 Printer* printer;
146 while ((printer = Printer::At(0)) != NULL) {
147 printer->AbortPrintThread();
148 UnregisterPrinter(printer);
149 }
150
151 // Wait for printers
152 if (fHasReferences > 0) {
153 acquire_sem(fHasReferences);
154 delete_sem(fHasReferences);
155 fHasReferences = 0;
156 }
157
158 delete [] fSelectedIcon;
159 fSelectedIcon = NULL;
160
161 return true;
162 }
163
164
165 void
Acquire()166 PrintServerApp::Acquire()
167 {
168 if (atomic_add(&fReferences, 1) == 0)
169 acquire_sem(fHasReferences);
170 }
171
172
173 void
Release()174 PrintServerApp::Release()
175 {
176 if (atomic_add(&fReferences, -1) == 1)
177 release_sem(fHasReferences);
178 }
179
180
181 void
RegisterPrinter(BDirectory * printer)182 PrintServerApp::RegisterPrinter(BDirectory* printer)
183 {
184 BString transport, address, connection, state;
185
186 if (printer->ReadAttrString(PSRV_PRINTER_ATTR_TRANSPORT, &transport) == B_OK
187 && printer->ReadAttrString(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, &address)
188 == B_OK
189 && printer->ReadAttrString(PSRV_PRINTER_ATTR_CNX, &connection) == B_OK
190 && printer->ReadAttrString(PSRV_PRINTER_ATTR_STATE, &state) == B_OK
191 && state == "free") {
192 BAutolock lock(gLock);
193 if (lock.IsLocked()) {
194 // check if printer is already registered
195 node_ref node;
196 if (printer->GetNodeRef(&node) != B_OK)
197 return;
198
199 if (Printer::Find(&node) != NULL)
200 return;
201
202 // register new printer
203 Resource* resource = fResourceManager.Allocate(transport.String(),
204 address.String(), connection.String());
205 AddHandler(new Printer(printer, resource));
206 Acquire();
207 }
208 }
209 }
210
211
212 void
UnregisterPrinter(Printer * printer)213 PrintServerApp::UnregisterPrinter(Printer* printer)
214 {
215 RemoveHandler(printer);
216 Printer::Remove(printer);
217 printer->Release();
218 }
219
220
221 void
NotifyPrinterDeletion(Printer * printer)222 PrintServerApp::NotifyPrinterDeletion(Printer* printer)
223 {
224 BAutolock lock(gLock);
225 if (lock.IsLocked()) {
226 fResourceManager.Free(printer->GetResource());
227 Release();
228 }
229 }
230
231
232 void
EntryCreated(node_ref * node,entry_ref * entry)233 PrintServerApp::EntryCreated(node_ref* node, entry_ref* entry)
234 {
235 BEntry printer(entry);
236 if (printer.InitCheck() == B_OK && printer.IsDirectory()) {
237 BDirectory dir(&printer);
238 if (dir.InitCheck() == B_OK) RegisterPrinter(&dir);
239 }
240 }
241
242
243 void
EntryRemoved(node_ref * node)244 PrintServerApp::EntryRemoved(node_ref* node)
245 {
246 Printer* printer = Printer::Find(node);
247 if (printer) {
248 if (printer == fDefaultPrinter)
249 fDefaultPrinter = NULL;
250 UnregisterPrinter(printer);
251 }
252 }
253
254
255 void
AttributeChanged(node_ref * node)256 PrintServerApp::AttributeChanged(node_ref* node)
257 {
258 BDirectory printer(node);
259 if (printer.InitCheck() == B_OK)
260 RegisterPrinter(&printer);
261 }
262
263
264 /*! This method builds the internal list of printers from disk. It
265 also installs a node monitor to be sure that the list keeps
266 updated with the definitions on disk.
267
268 @return B_OK if successful, or an errorcode if failed.
269 */
270 status_t
SetupPrinterList()271 PrintServerApp::SetupPrinterList()
272 {
273 // Find directory containing printer definition nodes
274 BPath path;
275 status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path);
276 if (status != B_OK)
277 return status;
278
279 // Directory has to exist in order to watch it
280 mode_t mode = 0777;
281 create_directory(path.Path(), mode);
282
283 BDirectory dir(path.Path());
284 status = dir.InitCheck();
285 if (status != B_OK)
286 return status;
287
288 // Register printer definition nodes
289 BEntry entry;
290 while(dir.GetNextEntry(&entry) == B_OK) {
291 if (!entry.IsDirectory())
292 continue;
293
294 BDirectory node(&entry);
295 BNodeInfo info(&node);
296 char buffer[256];
297 if (info.GetType(buffer) == B_OK
298 && strcmp(buffer, PSRV_PRINTER_FILETYPE) == 0) {
299 RegisterPrinter(&node);
300 }
301 }
302
303 // Now we are ready to start node watching
304 fFolder = new FolderWatcher(this, dir, true);
305 fFolder->SetListener(this);
306
307 return B_OK;
308 }
309
310
311 /*! Message handling method for print_server application class.
312
313 @param msg Actual message sent to application class.
314 */
315 void
MessageReceived(BMessage * msg)316 PrintServerApp::MessageReceived(BMessage* msg)
317 {
318 switch(msg->what) {
319 case PSRV_GET_ACTIVE_PRINTER:
320 case PSRV_MAKE_PRINTER_ACTIVE_QUIETLY:
321 case PSRV_MAKE_PRINTER_ACTIVE:
322 case PSRV_MAKE_PRINTER:
323 case PSRV_SHOW_PAGE_SETUP:
324 case PSRV_SHOW_PRINT_SETUP:
325 case PSRV_PRINT_SPOOLED_JOB:
326 case PSRV_GET_DEFAULT_SETTINGS:
327 Handle_BeOSR5_Message(msg);
328 break;
329
330 case B_GET_PROPERTY:
331 case B_SET_PROPERTY:
332 case B_CREATE_PROPERTY:
333 case B_DELETE_PROPERTY:
334 case B_COUNT_PROPERTIES:
335 case B_EXECUTE_PROPERTY:
336 HandleScriptingCommand(msg);
337 break;
338
339 default:
340 Inherited::MessageReceived(msg);
341 }
342 }
343
344
345 /*! Creates printer definition/spool directory. It sets the
346 attributes of the directory to the values passed and calls
347 the driver's add_printer method to handle any configuration
348 needed.
349
350 @param printerName Name of printer to create.
351 @param driverName Name of driver to use for this printer.
352 @param connection "Local" or "Network".
353 @param transportName Name of transport driver to use.
354 @param transportPath Configuration data for transport driver.
355 */
356 status_t
CreatePrinter(const char * printerName,const char * driverName,const char * connection,const char * transportName,const char * transportPath)357 PrintServerApp::CreatePrinter(const char* printerName, const char* driverName,
358 const char* connection, const char* transportName,
359 const char* transportPath)
360 {
361 // Find directory containing printer definitions
362 BPath path;
363 status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path, true,
364 NULL);
365 if (status != B_OK)
366 return status;
367
368 // Create our printer definition/spool directory
369 BDirectory printersDir(path.Path());
370 BDirectory printer;
371
372 status = printersDir.CreateDirectory(printerName, &printer);
373 if (status == B_FILE_EXISTS) {
374 printer.SetTo(&printersDir, printerName);
375
376 BString info;
377 char type[B_MIME_TYPE_LENGTH];
378 BNodeInfo(&printer).GetType(type);
379 if (strcmp(PSRV_PRINTER_FILETYPE, type) == 0) {
380 BPath path;
381 if (Printer::FindPathToDriver(printerName, &path) == B_OK) {
382 if (fDefaultPrinter) {
383 // the printer exists, but is not the default printer
384 if (strcmp(fDefaultPrinter->Name(), printerName) != 0)
385 status = B_OK;
386 return status;
387 }
388 // the printer exists, but no default at all
389 return B_OK;
390 } else {
391 info.SetTo(B_TRANSLATE(
392 "A printer with that name already exists, "
393 "but its driver could not be found! Replace?"));
394 }
395 } else {
396 info.SetTo(B_TRANSLATE(
397 "A printer with that name already exists, "
398 "but it's not usable at all! Replace?"));
399 }
400
401 if (info.Length() != 0) {
402 BAlert *alert = new BAlert("Info", info.String(),
403 B_TRANSLATE("Cancel"), B_TRANSLATE("OK"));
404 alert->SetShortcut(0, B_ESCAPE);
405 if (alert->Go() == 0)
406 return status;
407 }
408 } else if (status != B_OK) {
409 return status;
410 }
411
412 // Set its type to a printer
413 BNodeInfo info(&printer);
414 info.SetType(PSRV_PRINTER_FILETYPE);
415
416 // Store the settings in its attributes
417 printer.WriteAttr(PSRV_PRINTER_ATTR_PRT_NAME, B_STRING_TYPE, 0, printerName,
418 ::strlen(printerName) + 1);
419 printer.WriteAttr(PSRV_PRINTER_ATTR_DRV_NAME, B_STRING_TYPE, 0, driverName,
420 ::strlen(driverName) + 1);
421 printer.WriteAttr(PSRV_PRINTER_ATTR_TRANSPORT, B_STRING_TYPE, 0,
422 transportName, ::strlen(transportName) + 1);
423 printer.WriteAttr(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, B_STRING_TYPE, 0,
424 transportPath, ::strlen(transportPath) + 1);
425 printer.WriteAttr(PSRV_PRINTER_ATTR_CNX, B_STRING_TYPE, 0, connection,
426 ::strlen(connection) + 1);
427
428 status = Printer::ConfigurePrinter(driverName, printerName);
429 if (status == B_OK) {
430 // Notify printer driver that a new printer definition node
431 // has been created.
432 printer.WriteAttr(PSRV_PRINTER_ATTR_STATE, B_STRING_TYPE, 0, "free",
433 ::strlen("free")+1);
434 }
435
436 if (status != B_OK) {
437 BEntry entry;
438 if (printer.GetEntry(&entry) == B_OK)
439 entry.Remove();
440 }
441
442 return status;
443 }
444
445
446 /*! Makes a new printer the active printer. This is done simply
447 by changing our class attribute fDefaultPrinter, and changing
448 the icon of the BNode for the printer. Ofcourse, we need to
449 change the icon of the "old" default printer first back to a
450 "non-active" printer icon first.
451
452 @param printerName Name of the new active printer.
453 @return B_OK on success, or error code otherwise.
454 */
455 status_t
SelectPrinter(const char * printerName)456 PrintServerApp::SelectPrinter(const char* printerName)
457 {
458 // Find the node of the "old" default printer
459 BNode node;
460 if (fDefaultPrinter != NULL
461 && FindPrinterNode(fDefaultPrinter->Name(), node) == B_OK) {
462 // and remove the custom icon
463 BNodeInfo info(&node);
464 info.SetIcon(NULL, B_MINI_ICON);
465 info.SetIcon(NULL, B_LARGE_ICON);
466 }
467
468 // Find the node for the new default printer
469 status_t status = FindPrinterNode(printerName, node);
470 if (status == B_OK) {
471 // and add the custom icon
472 BNodeInfo info(&node);
473 info.SetIcon(fSelectedIcon, fIconSize);
474 }
475
476 fDefaultPrinter = Printer::Find(printerName);
477 StoreDefaultPrinter();
478 // update our pref file
479 be_roster->Broadcast(new BMessage(B_PRINTER_CHANGED));
480
481 return status;
482 }
483
484
485 //! Handles calling the printer drivers for printing a spooled job.
486 void
HandleSpooledJobs()487 PrintServerApp::HandleSpooledJobs()
488 {
489 const int n = Printer::CountPrinters();
490 for (int i = 0; i < n; i ++) {
491 Printer* printer = Printer::At(i);
492 printer->HandleSpooledJob();
493 }
494 }
495
496
497 /*! Loads the currently selected printer from a private settings
498 file.
499
500 @return Error code on failore, or B_OK if all went fine.
501 */
502 status_t
RetrieveDefaultPrinter()503 PrintServerApp::RetrieveDefaultPrinter()
504 {
505 fDefaultPrinter = Printer::Find(fSettings->DefaultPrinter());
506 return B_OK;
507 }
508
509
510 /*! Stores the currently selected printer in a private settings
511 file.
512
513 @return Error code on failore, or B_OK if all went fine.
514 */
515 status_t
StoreDefaultPrinter()516 PrintServerApp::StoreDefaultPrinter()
517 {
518 if (fDefaultPrinter)
519 fSettings->SetDefaultPrinter(fDefaultPrinter->Name());
520 else
521 fSettings->SetDefaultPrinter("");
522 return B_OK;
523 }
524
525
526 /*! Find the BNode representing the specified printer. It searches
527 *only* in the users printer definitions.
528
529 @param name Name of the printer to look for.
530 @param node BNode to set to the printer definition node.
531 @return B_OK if found, an error code otherwise.
532 */
533 status_t
FindPrinterNode(const char * name,BNode & node)534 PrintServerApp::FindPrinterNode(const char* name, BNode& node)
535 {
536 // Find directory containing printer definitions
537 BPath path;
538 status_t status = find_directory(B_USER_PRINTERS_DIRECTORY, &path, true,
539 NULL);
540 if (status != B_OK)
541 return status;
542
543 path.Append(name);
544 return node.SetTo(path.Path());
545 }
546
547
548 bool
OpenSettings(BFile & file,const char * name,bool forReading)549 PrintServerApp::OpenSettings(BFile& file, const char* name, bool forReading)
550 {
551 BPath path;
552 uint32 openMode = forReading ? B_READ_ONLY : B_CREATE_FILE | B_ERASE_FILE
553 | B_WRITE_ONLY;
554 return find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK
555 && path.Append(name) == B_OK
556 && file.SetTo(path.Path(), openMode) == B_OK;
557 }
558
559
560 void
LoadSettings()561 PrintServerApp::LoadSettings()
562 {
563 BFile file;
564 if (OpenSettings(file, kSettingsName, true)) {
565 fSettings->Load(&file);
566 fUseConfigWindow = fSettings->UseConfigWindow();
567 }
568 }
569
570
571 void
SaveSettings()572 PrintServerApp::SaveSettings()
573 {
574 BFile file;
575 if (OpenSettings(file, kSettingsName, false)) {
576 fSettings->SetUseConfigWindow(fUseConfigWindow);
577 fSettings->Save(&file);
578 }
579 }
580