1 //////////////////////////////////////////////////////////////////////////
2 //
3 // pgAdmin III - PostgreSQL Tools
4 //
5 // Copyright (C) 2002 - 2016, The pgAdmin Development Team
6 // This software is released under the PostgreSQL Licence
7 //
8 // frmMainConfig.cpp - Backend configuration tool
9 //
10 //////////////////////////////////////////////////////////////////////////
11
12 #include "pgAdmin3.h"
13
14 #ifdef __WXMSW__
15 #include <io.h>
16 #include <fcntl.h>
17 #endif
18
19 #include <wx/imaglist.h>
20
21 #include "ctl/ctlMenuToolbar.h"
22 #include "frm/frmMainConfig.h"
23 #include "frm/frmMain.h"
24 #include "dlg/dlgMainConfig.h"
25 #include "utils/utffile.h"
26 #include "schema/pgServer.h"
27 #include "frm/menu.h"
28 #include "utils/pgfeatures.h"
29
30 #include <wx/arrimpl.cpp>
31 WX_DEFINE_OBJARRAY(pgConfigOrgLineArray);
32
33 #define CTL_CFGVIEW 345
34
35
BEGIN_EVENT_TABLE(frmMainConfig,frmConfig)36 BEGIN_EVENT_TABLE(frmMainConfig, frmConfig)
37 EVT_MENU(MNU_UNDO, frmMainConfig::OnUndo)
38 EVT_MENU(MNU_CONTENTS, frmMainConfig::OnContents)
39 EVT_LIST_ITEM_ACTIVATED(CTL_CFGVIEW, frmMainConfig::OnEditSetting)
40 EVT_LIST_ITEM_SELECTED(CTL_CFGVIEW, frmMainConfig::OnSelectSetting)
41 END_EVENT_TABLE()
42
43
44 #define BCE_TITLE _("Backend Configuration Editor")
45
46
47 frmMainConfig::frmMainConfig(frmMain *parent, pgServer *server)
48 : frmConfig(parent, BCE_TITLE, 0)
49 {
50 wxString applicationname = appearanceFactory->GetLongAppName() + _(" - Configuration Editor");
51 if (server)
52 {
53 conn = server->CreateConn(wxEmptyString, 0, applicationname);
54 serverVersionNumber = server->GetVersionNumber();
55 }
56 else
57 serverVersionNumber = wxEmptyString;
58
59 InitForm();
60 Init();
61
62 if (conn)
63 {
64 if (serverFileName.IsEmpty())
65 serverFileName = wxT("postgresql.conf");
66
67
68 wxString txt;
69 txt.Printf(_(" - %s on %s (%s:%d)"),
70 serverFileName.c_str(), server->GetDescription().c_str(),
71 server->GetName().c_str(), server->GetPort());
72 SetTitle(BCE_TITLE + txt);
73
74 wxString str;
75 str = conn->ExecuteScalar(wxT("SELECT pg_file_read('") + serverFileName + wxT("', 0, ")
76 wxT("pg_file_length('") + serverFileName + wxT("'))"));
77
78 DisplayFile(str);
79
80 statusBar->SetStatusText(wxString::Format(_(" Configuration read from %s"), conn->GetHost().c_str()));
81 }
82 }
83
84
frmMainConfig(const wxString & title,const wxString & configFile)85 frmMainConfig::frmMainConfig(const wxString &title, const wxString &configFile)
86 : frmConfig(title + wxT(" - ") + _("Backend Configuration Editor"), configFile)
87 {
88 InitForm();
89 Init();
90 OpenLastFile();
91 }
92
93
~frmMainConfig()94 frmMainConfig::~frmMainConfig()
95 {
96 options.clear();
97 categories.clear();
98 lines.Clear();
99 }
100
101
Init(pgSettingReader * reader)102 void frmMainConfig::Init(pgSettingReader *reader)
103 {
104 pgSettingItem *item;
105 do
106 {
107 item = reader->GetNextItem();
108 if (item)
109 {
110 if (item->context == pgSettingItem::PGC_INTERNAL)
111 delete item;
112 else
113 {
114 if (options[item->name])
115 delete item;
116 else
117 {
118 options[item->name] = item;
119
120 wxArrayString *category = categories[item->category];
121 if (!category)
122 {
123 category = new wxArrayString;
124 categories[item->category] = category;
125 }
126 category->Add(item->name);
127 }
128 }
129 }
130 }
131 while (item);
132
133 editMenu->Enable(MNU_DELETE, false);
134 toolBar->EnableTool(MNU_DELETE, false);
135 }
136
137
Init()138 void frmMainConfig::Init()
139 {
140 // Cleanup first, in case we're re-initing
141 options.clear();
142 categories.clear();
143 lines.Clear();
144
145 pgSettingReader *reader;
146 if (conn)
147 {
148 // read settings from server
149 reader = new pgSettingDbReader(conn);
150 }
151 else
152 {
153 // read settings from file. First, use localized file...
154 reader = new pgSettingFileReader(true);
155
156 if (reader->IsValid())
157 Init(reader);
158 delete reader;
159
160 // ... then add default file
161 reader = new pgSettingFileReader(false);
162 }
163
164 if (reader->IsValid())
165 Init(reader);
166
167 delete reader;
168 }
169
InitForm()170 void frmMainConfig::InitForm()
171 {
172 appearanceFactory->SetIcons(this);
173
174 InitFrame(wxT("frmMainConfig"));
175 RestorePosition(50, 50, 600, 600, 300, 200);
176
177 cfgList = new ctlListView(this, CTL_CFGVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
178
179 cfgList->SetImageList(configImageList, wxIMAGE_LIST_SMALL);
180
181 cfgList->AddColumn(_("Setting name"), 120);
182 cfgList->AddColumn(_("Value"), 80);
183 if (conn)
184 cfgList->AddColumn(_("Current value"), 80);
185 cfgList->AddColumn(_("Comment"), 400);
186 }
187
OnSelectSetting(wxListEvent & event)188 void frmMainConfig::OnSelectSetting(wxListEvent &event)
189 {
190
191 // Disable undo because we don't want to undo the wrong line.
192 editMenu->Enable(MNU_UNDO, false);
193 toolBar->EnableTool(MNU_UNDO, false);
194 }
195
OnEditSetting(wxListEvent & event)196 void frmMainConfig::OnEditSetting(wxListEvent &event)
197 {
198 wxString name = cfgList->GetText(event.GetIndex());
199 if (!name.IsEmpty())
200 {
201 pgSettingItem *item = options[name];
202 wxASSERT(item);
203 dlgMainConfig dlg(this, item);
204 dlg.Go();
205
206 if (item->orgLine && !item->newLine->Differs(item->orgLine))
207 {
208 delete item->newLine;
209 item->newLine = 0;
210 }
211 else
212 {
213 changed = true;
214 fileMenu->Enable(MNU_SAVE, true);
215 editMenu->Enable(MNU_UNDO, true);
216 toolBar->EnableTool(MNU_SAVE, true);
217 toolBar->EnableTool(MNU_UNDO, true);
218
219
220 }
221 UpdateLine(event.GetIndex());
222 }
223 }
224
225
OnContents(wxCommandEvent & event)226 void frmMainConfig::OnContents(wxCommandEvent &event)
227 {
228 DisplayHelp(wxT("index"), HELP_PGADMIN);
229 }
230
231
GetHelpPage() const232 wxString frmMainConfig::GetHelpPage() const
233 {
234 wxString page;
235 if (page.IsEmpty())
236 page = wxT("runtime-config");
237
238 return page;
239 }
240
241
OnUndo(wxCommandEvent & ev)242 void frmMainConfig::OnUndo(wxCommandEvent &ev)
243 {
244 wxString name = cfgList->GetText(cfgList->GetSelection());
245 if (!name.IsEmpty())
246 {
247 pgSettingItem *item = options[name];
248 if (item->newLine)
249 {
250 delete item->newLine;
251 item->newLine = 0;
252 UpdateLine(cfgList->GetSelection());
253 }
254 }
255 }
256
257
258
UpdateLine(int pos)259 void frmMainConfig::UpdateLine(int pos)
260 {
261 wxString name = cfgList->GetText(pos);
262 if (!name.IsEmpty())
263 {
264 pgSettingItem *item = options[name];
265
266 pgConfigLine *line = item->newLine;
267 if (!line)
268 line = item->orgLine;
269
270 wxString value, comment;
271 int imgIndex = 0;
272 if (line)
273 {
274 value = line->value;
275 comment = line->comment;
276 if (!line->isComment)
277 imgIndex = 1;
278 }
279 cfgList->SetItem(pos, 1, value);
280
281 if (conn)
282 cfgList->SetItem(pos, 3, comment);
283 else
284 cfgList->SetItem(pos, 2, comment);
285
286 cfgList->SetItemImage(pos, imgIndex, imgIndex);
287 }
288 }
289
290
WriteFile(pgConn * conn)291 void frmMainConfig::WriteFile(pgConn *conn)
292 {
293
294 size_t i;
295
296 wxString str;
297 for (i = 0 ; i < lines.GetCount() ; i++)
298 str.Append(lines.Item(i).GetNewText() + wxT("\n"));
299
300 for (i = 0 ; i < (size_t)cfgList->GetItemCount() ; i++)
301 {
302 pgSettingItem *item = options[cfgList->GetText(i)];
303
304 if (item && item->newLine && item->newLine->item && !item->orgLine)
305 str.Append(item->newLine->GetNewText() + wxT("\n"));
306 }
307
308
309 if (DoWriteFile(str, conn))
310 {
311 changed = false;
312 fileMenu->Enable(MNU_SAVE, false);
313 editMenu->Enable(MNU_UNDO, false);
314 toolBar->EnableTool(MNU_SAVE, false);
315 toolBar->EnableTool(MNU_UNDO, false);
316
317 size_t i;
318 for (i = 0 ; i < (size_t)cfgList->GetItemCount() ; i++)
319 {
320 pgSettingItem *item = options[cfgList->GetText(i)];
321
322 if (item && item->newLine)
323 {
324 if (!item->orgLine)
325 {
326 item->orgLine = new pgConfigOrgLine(item->newLine);
327 lines.Add(item->orgLine);
328 item->orgLine->item = item;
329 }
330 else
331 {
332 item->orgLine->comment = item->newLine->comment;
333 item->orgLine->isComment = item->newLine->isComment;
334 item->orgLine->value = item->newLine->value;
335 item->orgLine->text = item->orgLine->GetNewText();
336 }
337 }
338 }
339
340
341 for (i = 0 ; i < lines.GetCount() ; i++)
342 {
343 pgConfigOrgLine &line = lines.Item(i);
344 if (line.item && line.item->newLine)
345 {
346 line.text = line.GetNewText();
347 delete line.item->newLine;
348 line.item->newLine = 0;
349 }
350 }
351 }
352 }
353
354
355
DisplayFile(const wxString & str)356 void frmMainConfig::DisplayFile(const wxString &str)
357 {
358 Init();
359
360 filetype = wxTextFileType_Unix;
361 wxStringTokenizer strtok;
362 wxArrayString *unknownCategory = 0;
363
364 if (str.Find('\r') >= 0)
365 {
366 if (str.Find(wxT("\n\r")) >= 0 || str.Find(wxT("\r\n")))
367 filetype = wxTextFileType_Dos;
368 else
369 filetype = wxTextFileType_Mac;
370
371 strtok.SetString(wxTextBuffer::Translate(str, wxTextFileType_Unix), wxT("\n"), wxTOKEN_RET_EMPTY);
372 }
373 else
374 strtok.SetString(str, wxT("\n"), wxTOKEN_RET_EMPTY);
375
376 while (strtok.HasMoreTokens())
377 {
378 pgConfigOrgLine *line = new pgConfigOrgLine(strtok.GetNextToken());
379 lines.Add(line);
380
381 // identify option
382 bool isComment = false;
383 const wxChar *p = line->text.c_str();
384
385 // identify keywords
386 while (*p && wxStrchr(wxT("# \n"), *p))
387 {
388 if (*p == '#')
389 isComment = true;
390 p++;
391 }
392
393 if (!*p)
394 isComment = true;
395
396 line->isComment = isComment;
397
398 const wxChar *p2 = p;
399 while (*p2 && *p2 != '#' && *p2 != ' ' && *p2 != '\t' && *p2 != '=')
400 p2++;
401
402 if (p2 != p)
403 {
404 wxString keyword = line->text.Mid(p - line->text.c_str(), p2 - p);
405
406 pgSettingItemHashmap::iterator it = options.find(keyword);
407
408 pgSettingItem *item;
409 if (it == options.end())
410 {
411 if (isComment)
412 continue;
413
414 item = new pgSettingItem;
415 item->name = keyword;
416 item->category = _("Unknown");
417 item->short_desc = _("Unknown option");
418 item->extra_desc = _("This option is present in the configuration file, but not known to the configuration tool.");
419 item->SetType(wxT("string"));
420
421 options[item->name] = item;
422
423 if (!unknownCategory)
424 {
425 unknownCategory = new wxArrayString;
426 categories[item->category] = unknownCategory;
427 }
428 unknownCategory->Add(item->name);
429 }
430 else
431 item = it->second;
432
433
434 if (!isComment || !item->orgLine || item->orgLine->isComment)
435 {
436 line->item = item;
437 if (item->orgLine)
438 item->orgLine->item = 0;
439 item->orgLine = line;
440 }
441 while (*p2 && *p2 != '=')
442 p2++;
443
444 p2++; // skip =
445 while (*p2 && wxStrchr(wxT(" \t"), *p2))
446 p2++;
447
448 wxChar quoteChar = 0;
449 if (wxStrchr(wxT("'\""), *p2))
450 quoteChar = *p2++;
451
452 const wxChar *p3 = p2;
453 while (*p3)
454 {
455 if (*p3 == quoteChar || (!quoteChar && wxStrchr(wxT(" \t#"), *p3)))
456 break;
457 p3++;
458 }
459 if (p2 != p3)
460 {
461 line->value = line->text.Mid(p2 - line->text.c_str(), p3 - p2);
462 if (quoteChar)
463 p3++;
464
465 const wxChar *p4 = p3;
466 while (*p4 && wxStrchr(wxT(" \t#"), *p4))
467 p4++;
468
469 line->commentIndent = line->text.Mid(p3 - line->text.c_str(), p4 - p3);
470 line->comment = p4;
471 }
472 }
473 }
474
475 cfgList->DeleteAllItems();
476
477 double versionNum;
478 bool versionRetVal = serverVersionNumber.ToDouble(&versionNum);
479
480 // we want to show options ordered by category/name
481 // category might be localized, and we want a distinct category ordering
482
483 FillList(wxT("listen_addresses")); // Connections and Authentication / Connection Settings
484 FillList(wxT("authentication_timeout")); // Connections and Authentication / Security and Authentication
485 FillList(wxT("check_function_bodies")); // Client Connection Defaults / Statement Behaviour
486 FillList(wxT("lc_messages")); // Client Connection Defaults / Locale and Formatting
487 if (versionRetVal && versionNum < 8.4)
488 FillList(wxT("explain_pretty_print")); // Client Connection Defaults / Other Defaults
489 FillList(wxT("enable_hashjoin")); // Query Tuning / Planner Method Configuration
490 FillList(wxT("cpu_operator_cost")); // Query Tuning / Planner Cost Constants
491 if (!conn || !conn->GetIsGreenplum()) // Greenplum doesn't have the Genetic Query Optimizer
492 FillList(wxT("geqo")); // Query Tuning / Genetic Query Optimizer
493 FillList(wxT("default_statistics_target")); // Query Tuning / Other Planner Options
494 FillList(wxT("deadlock_timeout")); // Lock Management
495 FillList(wxT("shared_buffers")); // Resource Usage / Memory
496 if (versionRetVal && versionNum < 8.4)
497 FillList(wxT("max_fsm_pages")); // Resource Usage / Free Space Map
498 FillList(wxT("bgwriter_delay")); // Resource Usage
499 FillList(wxT("max_files_per_process")); // Resource Usage / Kernel Resources
500 FillList(wxT("log_connections")); // Reporting and Logging / What to Log
501 FillList(wxT("client_min_messages")); // Reporting and Logging / When to Log
502 FillList(wxT("log_destination")); // Reporting and Logging / Where to Log
503 FillList(wxT("stats_command_string"), wxT("track_activities")); // Statistics / Query and Index Statistics Collector
504 FillList(wxT("log_executor_stats")); // Statistics / Monitoring
505 FillList(wxT("fsync")); // Write-Ahead Log / Settings
506 FillList(wxT("checkpoint_segments")); // Write-Ahead Log / Checkpoints
507 if (versionRetVal && versionNum <= 8.4)
508 FillList(wxT("add_missing_from")); // Version and Platform Compatibility / Previous PostgreSQL Version
509 FillList(wxT("transform_null_equals")); // Version and Platform Compatibility / Other Platforms and Clients
510 if (!conn || !conn->GetIsGreenplum()) // Greenplum doesn't have trace_notify visible
511 FillList(wxT("trace_notify")); // Developer Options
512 FillList(wxT("hba_file")); // Ungrouped
513
514
515 // for all we didn't get
516 while (!categories.empty())
517 {
518 pgCategoryHashmap::iterator it = categories.begin();
519 wxString missed = it->first;
520 FillList(it->second);
521 categories.erase(it);
522 }
523 }
524
525
FillList(const wxString & categoryMember,const wxString & altCategoryMember)526 void frmMainConfig::FillList(const wxString &categoryMember, const wxString &altCategoryMember)
527 {
528 pgSettingItem *categoryItem = options[categoryMember];
529
530 if (!categoryItem && !altCategoryMember.IsEmpty())
531 categoryItem = options[altCategoryMember];
532
533 wxASSERT_MSG(categoryItem, wxString::Format(wxT("%s/%s"), categoryMember.c_str(), altCategoryMember.c_str()));
534
535 if (categoryItem)
536 {
537 FillList(categories[categoryItem->category]);
538 categories.erase(categoryItem->category);
539 }
540 }
541
542
FillList(wxArrayString * category)543 void frmMainConfig::FillList(wxArrayString *category)
544 {
545 if (category)
546 {
547 size_t i;
548 for (i = 0 ; i < category->GetCount() ; i++)
549 {
550 pgSettingItem *item = options[category->Item(i)];
551 wxASSERT(item);
552
553 wxString value;
554 wxString comment;
555 int imgIndex = 0;
556 if (item->orgLine)
557 {
558 if (!item->orgLine->isComment)
559 imgIndex = 1;
560 value = item->orgLine->value;
561 comment = item->orgLine->comment;
562 comment.Replace(wxT("\t"), wxT(" "));
563 }
564 long pos = cfgList->AppendItem(imgIndex, item->name, value);
565 if (conn)
566 {
567 cfgList->SetItem(pos, 2, item->value);
568 cfgList->SetItem(pos, 3, comment);
569 }
570 else
571 cfgList->SetItem(pos, 2, comment);
572 }
573 delete category;
574 }
575 }
576
577
578
579 enum
580 {
581 HINT_LISTEN_ADDRESSES,
582 HINT_AUTOVACUUM_CFG
583 };
584
585
586 const wxChar *hintString[] =
587 {
588 _("The PostgreSQL server engine is currently configured to listen for local connections only.\nYou might want to check \"listen_addresses\" to enable accessing the server over the network too."),
589 _("The autovacuum backend process is not running.\nIt is recommended to enable it by setting 'track_counts' and 'autovacuum' to 'on' in PostgreSQL 8.3 and above or 'stats_start_collector', 'stats_row_level' and 'autovacuum' to 'on' in earlier versions.")
590 };
591
592
GetHintString()593 wxString frmMainConfig::GetHintString()
594 {
595 wxArrayInt hints;
596 size_t i;
597 int autovacuum = 0;
598 bool autovacuumPresent = false;
599
600 for (i = 0 ; i < (size_t)cfgList->GetItemCount() ; i++)
601 {
602 pgSettingItem *item = options[cfgList->GetText(i)];
603
604 if (item)
605 {
606 wxString name = item->name;
607 wxString value = item->GetActiveValue();
608 if (name == wxT("listen_addresses"))
609 {
610 if (value.IsEmpty() || value == wxT("localhost"))
611 hints.Add(HINT_LISTEN_ADDRESSES);
612 }
613 else if (name == wxT("autovacuum"))
614 {
615 autovacuumPresent = true;
616 if (StrToBool(value))
617 autovacuum++;
618 }
619 else if (name == wxT("stats_start_collector") || name == wxT("stats_row_level"))
620 {
621 if (StrToBool(value))
622 autovacuum++;
623 }
624 else if (name == wxT("track_counts"))
625 {
626 // Double increment, because track_counts in 8.3 is worth both previous options.
627 if (StrToBool(value))
628 autovacuum = autovacuum + 2;
629 }
630 }
631 }
632
633 if (autovacuumPresent && autovacuum < 3)
634 hints.Add(HINT_AUTOVACUUM_CFG);
635
636 wxString str;
637 for (i = 0 ; i < hints.GetCount() ; i++)
638 {
639 if (i)
640 str.Append(wxT("\n\n"));
641 str.Append(hintString[hints.Item(i)]);
642 }
643
644 return str;
645 }
646
OnOpen(wxCommandEvent & event)647 void frmMainConfig::OnOpen(wxCommandEvent &event)
648 {
649 if (CheckChanged(true))
650 return;
651
652 #ifdef __WXMSW__
653 wxFileDialog dlg(this, _("Open configuration file"), lastDir, wxT(""),
654 _("Configuration files (*.conf)|*.conf|All files (*.*)|*.*"), wxFD_OPEN);
655 #else
656 wxFileDialog dlg(this, _("Open configuration file"), lastDir, wxT(""),
657 _("Configuration files (*.conf)|*.conf|All files (*)|*"), wxFD_OPEN);
658 #endif
659 if (dlg.ShowModal() == wxID_OK)
660 {
661 Init();
662
663 lastFilename = dlg.GetFilename();
664 lastDir = dlg.GetDirectory();
665 lastPath = dlg.GetPath();
666 OpenLastFile();
667 UpdateRecentFiles();
668 }
669 }
670
mainConfigFactory(menuFactoryList * list,wxMenu * mnu,ctlMenuToolbar * toolbar)671 mainConfigFactory::mainConfigFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : actionFactory(list)
672 {
673 mnu->Append(id, wxT("postgresql.conf"), _("Edit general server configuration file."));
674 }
675
676
StartDialog(frmMain * form,pgObject * obj)677 wxWindow *mainConfigFactory::StartDialog(frmMain *form, pgObject *obj)
678 {
679 pgServer *server = obj->GetServer();
680 if (server)
681 {
682 frmConfig *frm = new frmMainConfig(form, server);
683 frm->Go();
684 return frm;
685 }
686 return 0;
687 }
688
689
CheckEnable(pgObject * obj)690 bool mainConfigFactory::CheckEnable(pgObject *obj)
691 {
692 if (obj)
693 {
694 pgServer *server = obj->GetServer();
695 if (server)
696 {
697 pgConn *conn = server->GetConnection();
698 return conn && server->GetSuperUser() && conn->HasFeature(FEATURE_FILEREAD);
699 }
700 }
701 return false;
702 }
703
704
mainConfigFileFactory(menuFactoryList * list,wxMenu * mnu,ctlMenuToolbar * toolbar)705 mainConfigFileFactory::mainConfigFileFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : actionFactory(list)
706 {
707 mnu->Append(id, _("Open postgresql.conf..."), _("Open configuration editor with postgresql.conf."));
708 }
709
710
StartDialog(frmMain * form,pgObject * obj)711 wxWindow *mainConfigFileFactory::StartDialog(frmMain *form, pgObject *obj)
712 {
713 frmConfig *dlg = new frmMainConfig(form);
714 dlg->Go();
715 dlg->DoOpen();
716 return dlg;
717 }
718