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  MythDB::DBError("StorageGroup::Load getting local group names",
371  query);
372  else
373  {
374  while (query.next())
375  names << query.value(0).toString();
376  }
377 
378  query.prepare("SELECT distinct groupname "
379  "FROM storagegroup "
380  "ORDER BY groupname;");
381  if (!query.exec())
382  MythDB::DBError("StorageGroup::Load getting all group names",
383  query);
384  else
385  {
386  while (query.next())
387  masterNames << query.value(0).toString();
388  }
389 
390  clearSettings();
391 
392  if (isMaster || names.contains("Default"))
393  {
394  AddSelection(tr("Default", "Default storage group"),
395  "Default");
396  }
397  else
398  createAddDefaultButton = true;
399 
400  int curGroup = 0;
401  QString groupName;
402  while (curGroup < StorageGroup::kSpecialGroups.size())
403  {
404  groupName = StorageGroup::kSpecialGroups[curGroup];
405  if (names.contains(groupName))
406  {
407  addChild(new StorageGroupEditor(groupName));
408  createAddSpecialGroupButton[curGroup] = false;
409  }
410  else
411  createAddSpecialGroupButton[curGroup] = true;
412  curGroup++;
413  }
414 
415  int curName = 0;
416  while (curName < names.size())
417  {
418  if ((names[curName] != "Default") &&
419  (!StorageGroup::kSpecialGroups.contains(names[curName])))
420  addChild(new StorageGroupEditor(names[curName]));
421  curName++;
422  }
423 
424  if (createAddDefaultButton)
425  {
426  AddSelection(tr("(Create default group)"), "Default");
427  }
428 
429  curGroup = 0;
430  while (curGroup < StorageGroup::kSpecialGroups.size())
431  {
432  groupName = StorageGroup::kSpecialGroups[curGroup];
433  if (createAddSpecialGroupButton[curGroup])
434  AddSelection(tr("(Create %1 group)")
435  .arg(QCoreApplication::translate("(StorageGroups)",
436  groupName.toLatin1().constData())),
437  groupName);
438  curGroup++;
439  }
440 
441  if (isMaster)
442  {
443  auto *newGroup = new ButtonStandardSetting(tr("(Create new group)"));
444  connect(newGroup, SIGNAL(clicked()), SLOT(ShowNewGroupDialog()));
445  addChild(newGroup);
446  }
447  else
448  {
449  curName = 0;
450  while (curName < masterNames.size())
451  {
452  if ((masterNames[curName] != "Default") &&
453  (!StorageGroup::kSpecialGroups.contains(masterNames[curName])) &&
454  (!names.contains(masterNames[curName])))
455  AddSelection(tr("(Create %1 group)")
456  .arg(masterNames[curName]),
457  masterNames[curName]);
458  curName++;
459  }
460  }
461 
463 }
464 
465 
467 {
468  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
469  auto *settingdialog = new MythTextInputDialog(popupStack,
470  tr("Enter the name of the new storage group"));
471 
472  if (settingdialog->Create())
473  {
474  connect(settingdialog, SIGNAL(haveResult(QString)),
475  SLOT(CreateNewGroup(QString)));
476  popupStack->AddScreen(settingdialog);
477  }
478  else
479  {
480  delete settingdialog;
481  }
482 }
483 
484 void StorageGroupListEditor::CreateNewGroup(const QString& name)
485 {
486  auto *button = new StorageGroupEditor(name);
487  button->setLabel(name + tr(" Storage Group Directories"));
488  button->Load();
489  addChild(button);
490  emit settingsChanged(this);
491 }
492 
493 /* vim: set expandtab tabstop=4 shiftwidth=4: */
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
bool keyPressEvent(QKeyEvent *event) override
virtual bool keyPressEvent(QKeyEvent *)
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
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:50
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:887
void DoDeleteSlot(bool doDelete)
virtual void setLabel(QString str)
StorageUser * m_user
Definition: mythstorage.h:46
bool isActive(void) const
Definition: mythdbcon.h:204
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
StorageGroupDirSetting(int id, const QString &group)
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:807
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)