MythTV  master
storagegroupeditor.cpp
Go to the documentation of this file.
1 // Qt headers
2 #include <QCoreApplication>
3 #include <QDir>
4 #include <QFile>
5 #include <QRegExp>
6 #include <QUrl>
7 #include <QVector>
8 #include <utility>
9 
10 // MythTV headers
11 #include "storagegroupeditor.h"
12 #include "mythcorecontext.h"
13 #include "mythdb.h"
14 #include "mythlogging.h"
15 #include "mythdate.h"
16 #include "mythuifilebrowser.h"
17 
18 #define LOC QString("SGE(%1): ").arg(m_groupname)
19 
20 /****************************************************************************/
21 
23  m_group(std::move(group))
24 {
25  SetLabel();
26 }
27 
29 {
30  QString dispGroup = m_group;
31 
32  if (m_group == "Default")
33  dispGroup = tr("Default", "Default storage group");
34  else if (StorageGroup::kSpecialGroups.contains(m_group))
35  dispGroup = QCoreApplication::translate("(StorageGroups)",
36  m_group.toLatin1().constData());
37 
39  {
40  setLabel(tr("'%1' Storage Group Directories").arg(dispGroup));
41  }
42  else
43  {
44  setLabel(tr("Local '%1' Storage Group Directories").arg(dispGroup));
45  }
46 }
47 
49 {
50  QStringList actions;
51  bool handled =
52  GetMythMainWindow()->TranslateKeyPress("Global", e, actions);
53  for (int i = 0; i < actions.size() && !handled; i++)
54  {
55  QString action = actions[i];
56 
57  if (action == "DELETE")
58  {
59  handled = true;
61  }
62  }
63  if (handled)
64  return handled;
66 }
67 
69 {
70  return true;
71 }
72 
74 {
75  bool is_master_host = gCoreContext->IsMasterHost();
76 
77  QString dispGroup = m_group;
78  if (m_group == "Default")
79  dispGroup = tr("Default", "Default storage group");
80  else if (StorageGroup::kSpecialGroups.contains(m_group))
81  dispGroup = QCoreApplication::translate("(StorageGroups)",
82  m_group.toLatin1().constData());
83 
84  QString message = tr("Delete '%1' Storage Group?").arg(dispGroup);
85  if (is_master_host)
86  {
87  if (m_group == "Default")
88  {
89  message = tr("Delete '%1' Storage Group?\n(from remote hosts)").arg(dispGroup);
90  }
91  else
92  {
93  message = tr("Delete '%1' Storage Group?\n(from all hosts)").arg(dispGroup);
94  }
95  }
96 
97  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
98  auto *confirmDelete = new MythConfirmationDialog(popupStack, message, true);
99  if (confirmDelete->Create())
100  {
101  connect(confirmDelete, SIGNAL(haveResult(bool)),
102  SLOT(DoDeleteSlot(bool)));
103  popupStack->AddScreen(confirmDelete);
104  }
105  else
106  delete confirmDelete;
107 }
108 
110 {
111  if (doDelete)
112  {
113  bool is_master_host = gCoreContext->IsMasterHost();
114  MSqlQuery query(MSqlQuery::InitCon());
115  QString sql = "DELETE FROM storagegroup "
116  "WHERE groupname = :NAME";
117  if (is_master_host)
118  {
119  // From the master host, delete the group completely (versus just
120  // local directory list) unless it's the Default group, then just
121  // delete remote overrides of the Default group
122  if (m_group == "Default")
123  sql.append(" AND hostname != :HOSTNAME");
124  }
125  else
126  {
127  // For non-master hosts, delete only the local override of the
128  // group directory list
129  sql.append(" AND hostname = :HOSTNAME");
130  }
131  sql.append(';');
132  query.prepare(sql);
133  query.bindValue(":NAME", m_group);
134  if (!is_master_host || (m_group == "Default"))
135  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
136  if (!query.exec())
137  MythDB::DBError("StorageGroupListEditor::doDelete", query);
138  }
139 }
140 
142  int id,
143  QString group) :
144  SimpleDBStorage(_user, "storagegroup", "dirname"),
145  m_id(id),
146  m_group(std::move(group))
147 {
148 }
149 
151 {
152  QString dirnameTag(":SETDIRNAME");
153 
154  QString query("dirname = " + dirnameTag);
155 
156  bindings.insert(dirnameTag, m_user->GetDBValue());
157 
158  return query;
159 }
160 
162 {
163  QString hostnameTag(":WHEREHOST");
164  QString idTag(":WHEREID");
165 
166  QString query("hostname = " + hostnameTag + " AND id = " + idTag);
167 
168  bindings.insert(hostnameTag, gCoreContext->GetHostName());
169  bindings.insert(idTag, m_id);
170 
171  return query;
172 }
173 
174 StorageGroupDirSetting::StorageGroupDirSetting(int id, const QString &group) :
175  MythUIFileBrowserSetting(new StorageGroupDirStorage(this, id, group)),
176  m_id(id), m_group(group)
177 {
178  SetTypeFilter(QDir::AllDirs | QDir::Drives);
179 }
180 
182 {
183  QStringList actions;
184  bool handled =
185  GetMythMainWindow()->TranslateKeyPress("Global", event, actions);
186  for (int i = 0; i < actions.size() && !handled; i++)
187  {
188  QString action = actions[i];
189 
190  if (action == "DELETE")
191  {
192  handled = true;
194  }
195  }
196  return handled;
197 }
198 
200 {
201  QString message =
202  tr("Remove '%1'\nDirectory From Storage Group?").arg(getValue());
203  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
204  auto *confirmDelete = new MythConfirmationDialog(popupStack, message, true);
205 
206  if (confirmDelete->Create())
207  {
208  connect(confirmDelete, SIGNAL(haveResult(bool)),
209  SLOT(DoDeleteSlot(bool)));
210  popupStack->AddScreen(confirmDelete);
211  }
212  else
213  delete confirmDelete;
214 }
215 
217 {
218  if (doDelete)
219  {
220  MSqlQuery query(MSqlQuery::InitCon());
221  query.prepare("DELETE FROM storagegroup "
222  "WHERE groupname = :NAME "
223  "AND dirname = :DIRNAME "
224  "AND hostname = :HOSTNAME;");
225  query.bindValue(":NAME", m_group);
226  query.bindValue(":DIRNAME", getValue());
227  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
228  if (query.exec())
229  getParent()->removeChild(this);
230  else
231  MythDB::DBError("StorageGroupEditor::DoDeleteSlot", query);
232  }
233 }
234 
236 {
237  clearSettings();
238 
239  auto *button = new ButtonStandardSetting(tr("(Add New Directory)"));
240  connect(button, SIGNAL(clicked()), SLOT(ShowFileBrowser()));
241  addChild(button);
242 
243  MSqlQuery query(MSqlQuery::InitCon());
244  query.prepare("SELECT dirname, id FROM storagegroup "
245  "WHERE groupname = :NAME AND hostname = :HOSTNAME "
246  "ORDER BY id;");
247  query.bindValue(":NAME", m_group);
248  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
249  if (!query.exec() || !query.isActive())
250  MythDB::DBError("StorageGroupEditor::Load", query);
251  else
252  {
253  bool first = true;
254  QString dirname;
255  while (query.next())
256  {
257  /* The storagegroup.dirname column uses utf8_bin collation, so Qt
258  * uses QString::fromAscii() for toString(). Explicitly convert the
259  * value using QString::fromUtf8() to prevent corruption. */
260  dirname = QString::fromUtf8(query.value(0)
261  .toByteArray().constData());
262  if (first)
263  {
264  first = false;
265  }
266  addChild(new StorageGroupDirSetting(query.value(1).toInt(),
267  m_group));
268  }
269 
270  if (!first)
271  {
272  SetLabel();
273  }
274  }
275 
277 }
278 
280 {
281  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
282 
283  auto *settingdialog = new MythUIFileBrowser(popupStack, "");
284  settingdialog->SetTypeFilter(QDir::AllDirs | QDir::Drives);
285 
286  if (settingdialog->Create())
287  {
288  settingdialog->SetReturnEvent(this, "editsetting");
289  popupStack->AddScreen(settingdialog);
290  }
291  else
292  delete settingdialog;
293 }
294 
296 {
297  if (event->type() == DialogCompletionEvent::kEventType)
298  {
299  auto *dce = dynamic_cast<DialogCompletionEvent*>(event);
300  if (dce == nullptr)
301  return;
302  QString resultid = dce->GetId();
303 
304  if (resultid == "editsetting")
305  {
306  QString dirname = dce->GetResultText();
307 
308  if (dirname.isEmpty())
309  return;
310 
311  if (!dirname.endsWith("/"))
312  dirname.append("/");
313 
314  MSqlQuery query(MSqlQuery::InitCon());
315  query.prepare("INSERT INTO storagegroup (groupname, hostname, dirname) "
316  "VALUES (:NAME, :HOSTNAME, :DIRNAME);");
317  query.bindValue(":NAME", m_group);
318  query.bindValue(":DIRNAME", dirname);
319  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
320  if (!query.exec())
321  MythDB::DBError("StorageGroupEditor::customEvent", query);
322  else
323  {
324  SetLabel();
325  StandardSetting *directory =
326  new StorageGroupDirSetting(query.lastInsertId().toInt(),
327  m_group);
328  directory->setValue(dirname);
329  addChild(directory);
330  emit settingsChanged(this);
331  }
332  }
333  }
334 }
335 
336 
337 /****************************************************************************/
338 
340 {
341  if (gCoreContext->IsMasterHost())
342  setLabel(tr("Storage Groups (directories for new recordings)"));
343  else
344  setLabel(tr("Local Storage Groups (directories for new recordings)"));
345 }
346 
347 void StorageGroupListEditor::AddSelection(const QString &label,
348  const QString &value)
349 {
350  auto *button = new StorageGroupEditor(value);
351  button->setLabel(label);
352  addChild(button);
353 }
354 
356 {
357  QStringList names;
358  QStringList masterNames;
359  bool createAddDefaultButton = false;
360  QVector< bool > createAddSpecialGroupButton( StorageGroup::kSpecialGroups.size() );
361  bool isMaster = gCoreContext->IsMasterHost();
362 
363  MSqlQuery query(MSqlQuery::InitCon());
364  query.prepare("SELECT distinct groupname "
365  "FROM storagegroup "
366  "WHERE hostname = :HOSTNAME "
367  "ORDER BY groupname;");
368  query.bindValue(":HOSTNAME", gCoreContext->GetHostName());
369  if (!query.exec())
370  {
371  MythDB::DBError("StorageGroup::Load getting local group names",
372  query);
373  }
374  else
375  {
376  while (query.next())
377  names << query.value(0).toString();
378  }
379 
380  query.prepare("SELECT distinct groupname "
381  "FROM storagegroup "
382  "ORDER BY groupname;");
383  if (!query.exec())
384  {
385  MythDB::DBError("StorageGroup::Load getting all group names",
386  query);
387  }
388  else
389  {
390  while (query.next())
391  masterNames << query.value(0).toString();
392  }
393 
394  clearSettings();
395 
396  if (isMaster || names.contains("Default"))
397  {
398  AddSelection(tr("Default", "Default storage group"),
399  "Default");
400  }
401  else
402  createAddDefaultButton = true;
403 
404  int curGroup = 0;
405  QString groupName;
406  while (curGroup < StorageGroup::kSpecialGroups.size())
407  {
408  groupName = StorageGroup::kSpecialGroups[curGroup];
409  if (names.contains(groupName))
410  {
411  addChild(new StorageGroupEditor(groupName));
412  createAddSpecialGroupButton[curGroup] = false;
413  }
414  else
415  createAddSpecialGroupButton[curGroup] = true;
416  curGroup++;
417  }
418 
419  int curName = 0;
420  while (curName < names.size())
421  {
422  if ((names[curName] != "Default") &&
423  (!StorageGroup::kSpecialGroups.contains(names[curName])))
424  addChild(new StorageGroupEditor(names[curName]));
425  curName++;
426  }
427 
428  if (createAddDefaultButton)
429  {
430  AddSelection(tr("(Create default group)"), "Default");
431  }
432 
433  curGroup = 0;
434  while (curGroup < StorageGroup::kSpecialGroups.size())
435  {
436  groupName = StorageGroup::kSpecialGroups[curGroup];
437  if (createAddSpecialGroupButton[curGroup])
438  {
439  AddSelection(tr("(Create %1 group)")
440  .arg(QCoreApplication::translate("(StorageGroups)",
441  groupName.toLatin1().constData())),
442  groupName);
443  }
444  curGroup++;
445  }
446 
447  if (isMaster)
448  {
449  auto *newGroup = new ButtonStandardSetting(tr("(Create new group)"));
450  connect(newGroup, SIGNAL(clicked()), SLOT(ShowNewGroupDialog()));
451  addChild(newGroup);
452  }
453  else
454  {
455  curName = 0;
456  while (curName < masterNames.size())
457  {
458  if ((masterNames[curName] != "Default") &&
459  (!StorageGroup::kSpecialGroups.contains(masterNames[curName])) &&
460  (!names.contains(masterNames[curName])))
461  {
462  AddSelection(tr("(Create %1 group)")
463  .arg(masterNames[curName]),
464  masterNames[curName]);
465  }
466  curName++;
467  }
468  }
469 
471 }
472 
473 
475 {
476  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
477  auto *settingdialog = new MythTextInputDialog(popupStack,
478  tr("Enter the name of the new storage group"));
479 
480  if (settingdialog->Create())
481  {
482  connect(settingdialog, SIGNAL(haveResult(QString)),
483  SLOT(CreateNewGroup(QString)));
484  popupStack->AddScreen(settingdialog);
485  }
486  else
487  {
488  delete settingdialog;
489  }
490 }
491 
492 void StorageGroupListEditor::CreateNewGroup(const QString& name)
493 {
494  auto *button = new StorageGroupEditor(name);
495  button->setLabel(name + tr(" Storage Group Directories"));
496  button->Load();
497  addChild(button);
498  emit settingsChanged(this);
499 }
500 
501 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:783
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:864
bool keyPressEvent(QKeyEvent *event) override
Dialog asking for user confirmation.
virtual void clearSettings()
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
QString GetWhereClause(MSqlBindings &bindings) const override
virtual bool keyPressEvent(QKeyEvent *event)
StandardSetting * getParent() const
void settingsChanged(StandardSetting *selectedSetting=nullptr)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
virtual void Load(void)
void AddSelection(const QString &label, const QString &value)
MythScreenStack * GetStack(const QString &stackname)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static const QStringList kSpecialGroups
Definition: storagegroup.h:46
virtual QString GetDBValue(void) const =0
static Type kEventType
Definition: mythdialogbox.h:56
virtual QString getValue(void) const
QVariant value(int i) const
Definition: mythdbcon.h:198
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
QMap< QString, QVariant > MSqlBindings
typedef for a map of string -> string bindings for generic queries.
Definition: mythdbcon.h:98
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:888
void DoDeleteSlot(bool doDelete)
virtual void setLabel(QString str)
StorageUser * m_user
Definition: mythstorage.h:50
bool isActive(void) const
Definition: mythdbcon.h:204
StorageGroupDirSetting(int id, const QString &group)
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
void CreateNewGroup(const QString &name)
bool IsMasterHost(void)
is this the same host as the master
virtual void addChild(StandardSetting *child)
MythMainWindow * GetMythMainWindow(void)
Dialog prompting the user to enter a text string.
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:808
void DoDeleteSlot(bool doDelete)
StorageGroupDirStorage(StorageUser *_user, int id, QString group)
void SetTypeFilter(QDir::Filters filter)
void Load(void) override
bool canDelete(void) override
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
void Load(void) override
StorageGroupEditor(QString group)
QString GetHostName(void)
virtual void setValue(const QString &newValue)
QString GetSetClause(MSqlBindings &bindings) const override
void customEvent(QEvent *event) override
bool keyPressEvent(QKeyEvent *event) override
virtual void removeChild(StandardSetting *child)