MythTV master
custompriority.cpp
Go to the documentation of this file.
1// Qt
2#include <QSqlError>
3
4// MythTV
16
17// MythFrontend
18#include "custompriority.h"
19#include "viewschedulediff.h"
20
22 : MythScreenType(parent, "CustomPriority")
23{
24 if (proginfo)
25 m_pginfo = new ProgramInfo(*proginfo);
26 else
27 m_pginfo = new ProgramInfo();
28
30}
31
33{
34 delete m_pginfo;
35
37}
38
40{
41 if (!LoadWindowFromXML("schedule-ui.xml", "custompriority", this))
42 return false;
43
44 m_ruleList = dynamic_cast<MythUIButtonList *>(GetChild("rules"));
45 m_clauseList = dynamic_cast<MythUIButtonList *>(GetChild("clauses"));
46
47 m_prioritySpin = dynamic_cast<MythUISpinBox *>(GetChild("priority"));
48
49 m_titleEdit = dynamic_cast<MythUITextEdit *>(GetChild("title"));
50 m_descriptionEdit = dynamic_cast<MythUITextEdit *>(GetChild("description"));
51
52 m_addButton = dynamic_cast<MythUIButton *>(GetChild("add"));
53 m_installButton = dynamic_cast<MythUIButton *>(GetChild("install"));
54 m_testButton = dynamic_cast<MythUIButton *>(GetChild("test"));
55 m_deleteButton = dynamic_cast<MythUIButton *>(GetChild("delete"));
56 m_cancelButton = dynamic_cast<MythUIButton *>(GetChild("cancel"));
57
61 {
62 LOG(VB_GENERAL, LOG_ERR,
63 "CustomPriority, theme is missing required elements");
64 return false;
65 }
66
69
74
80
81 loadData();
82
84
85 return true;
86}
87
89{
90 QString baseTitle = m_pginfo->GetTitle();
91 baseTitle.remove(RecordingInfo::kReSearchTypeName);
92
93 QString quoteTitle = baseTitle;
94 quoteTitle.replace("\'","\'\'");
95
96 m_prioritySpin->SetRange(-99,99,1);
98
99 RuleInfo rule;
100 rule.priority = QString().setNum(1);
101
102 new MythUIButtonListItem(m_ruleList, tr("<New priority rule>"),
103 QVariant::fromValue(rule));
104
106 result.prepare("SELECT priorityname, recpriority, selectclause "
107 "FROM powerpriority ORDER BY priorityname;");
108
109 if (result.exec())
110 {
111 MythUIButtonListItem *item = nullptr;
112 while (result.next())
113 {
114 QString trimTitle = result.value(0).toString();
115 trimTitle.remove(RecordingInfo::kReSearchTypeName);
116
117 rule.title = trimTitle;
118 rule.priority = result.value(1).toString();
119 rule.description = result.value(2).toString();
120
121 item = new MythUIButtonListItem(m_ruleList, rule.title,
122 QVariant::fromValue(rule));
123
124 if (trimTitle == baseTitle)
126 }
127 }
128 else
129 {
130 MythDB::DBError("Get power search rules query", result);
131 }
132
133 // No memory leak. In the previous while loop, MythUIButtonListItem
134 // adds the new item into m_ruleList.
135 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
137
138 if (!m_pginfo->GetTitle().isEmpty())
139 {
140 m_titleEdit->SetText(baseTitle);
141 m_descriptionEdit->SetText("program.title = '" + quoteTitle + "' ");
142 textChanged();
143 }
144
145 if (m_titleEdit->GetText().isEmpty())
147 else
149}
150
152{
153 QMap<QString, QString> examples;
154 examples.insert(tr("Modify priority for an input (Input priority)"),
155 "capturecard.cardid = 1");
156 examples.insert(tr("Modify priority for every card on a host"),
157 "capturecard.hostname = 'mythbox'");
158 examples.insert(tr("Only one specific channel ID (Channel priority)"),
159 "channel.chanid = '1003' ");
160 examples.insert(tr("Only a certain channel number"),
161 "channel.channum = '3' ");
162 examples.insert(tr("Only channels that carry a specific station"),
163 "channel.callsign = 'ESPN' ");
164 examples.insert(tr("Match related callsigns"),
165 "channel.callsign LIKE 'HBO%' ");
166 examples.insert(tr("Only channels marked as commercial free"),
167 QString("channel.commmethod = %1 ")
169 examples.insert(tr("Modify priority for a station on an input"),
170 "channel.callsign = 'ESPN' AND capturecard.cardid = 2");
171 examples.insert(tr("Priority for all matching titles"),
172 "program.title LIKE 'CSI: %' ");
173 examples.insert(tr("Only shows marked as HDTV"),
174 "program.hdtv > 0 ");
175 examples.insert(tr("Close Captioned priority"),
176 "program.closecaptioned > 0 ");
177 examples.insert(tr("New episodes only"),
178 "program.previouslyshown = 0 ");
179 examples.insert(tr("Modify unidentified episodes"),
180 "program.generic = 0 ");
181 examples.insert(tr("First showing of each episode"),
182 "program.first > 0 ");
183 examples.insert(tr("Last showing of each episode"),
184 "program.last > 0 ");
185 examples.insert(tr("Priority for any show with End Late time"),
186 "RECTABLE.endoffset > 0 ");
187 examples.insert(tr("Priority for a category"),
188 "program.category = 'Reality' ");
189 examples.insert(QString("%1 ('movie', 'series', 'sports', 'tvshow')")
190 .arg(tr("Priority for a category type")),
191 "program.category_type = 'sports' ");
192 examples.insert(tr("Modify priority by star rating (0.0 to 1.0 for "
193 "movies only)"),
194 "program.stars >= 0.75 ");
195 examples.insert(tr("Priority when shown once (complete example)"),
196 "program.first > 0 AND program.last > 0");
197 examples.insert(tr("Prefer a host for a storage group (complete example)"),
198 QString("RECTABLE.storagegroup = 'Archive' "
199 "AND capturecard.hostname = 'mythbox' "));
200 examples.insert(tr("Priority for HD shows under two hours (complete "
201 "example)"),
202 "program.hdtv > 0 AND program.starttime > "
203 "DATE_SUB(program.endtime, INTERVAL 2 HOUR) ");
204 examples.insert(tr("Priority for movies by the year of release (complete "
205 "example)"),
206 "program.category_type = 'movie' "
207 "AND program.airdate >= 2006 ");
208 examples.insert(tr("Prefer movies when shown at night (complete example)"),
209 "program.category_type = 'movie' "
210 "AND HOUR(program.starttime) < 6 ");
211 examples.insert(tr("Prefer a host for live sports with overtime (complete "
212 "example)"),
213 "RECTABLE.endoffset > 0 "
214 "AND program.category = 'Sports event' "
215 "AND capturecard.hostname = 'mythbox' ");
216 examples.insert(tr("Avoid poor signal quality (complete example)"),
217 "capturecard.cardid = 1 AND "
218 "channel.channum IN (3, 5, 39, 66) ");
219
220 for (auto it = examples.cbegin(); it != examples.cend(); ++it)
221 {
223 QVariant::fromValue(it.value()));
224 }
225}
226
228{
229 if (!item)
230 return;
231
232 auto rule = item->GetData().value<RuleInfo>();
233
234 m_titleEdit->SetText(rule.title);
235
236 m_descriptionEdit->SetText(rule.description);
237 m_prioritySpin->SetValue(rule.priority);
239 textChanged();
240}
241
243{
244 bool hastitle = !m_titleEdit->GetText().isEmpty();
245 bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
246
247 m_testButton->SetEnabled(hasdesc);
248 m_installButton->SetEnabled(hastitle && hasdesc);
249}
250
252{
254
255 if (!item)
256 return;
257
258 QString clause;
259
260 QString desc = m_descriptionEdit->GetText();
261
262 static const QRegularExpression kNonWhiteSpaceRE { "\\S" };
263 if (desc.contains(kNonWhiteSpaceRE))
264 clause = "AND ";
265 clause += item->GetData().toString();
266 m_descriptionEdit->SetText(desc.append(clause));
267}
268
270{
271 if (!checkSyntax())
272 return;
273
274 testSchedule();
275}
276
278{
279 if (!checkSyntax())
280 return;
281
283 if (!item)
284 return;
285
287 query.prepare("DELETE FROM powerpriority WHERE priorityname = :NAME;");
288 query.bindValue(":NAME", m_titleEdit->GetText());
289
290 if (!query.exec())
291 MythDB::DBError("Install power search delete", query);
292
293 query.prepare("INSERT INTO powerpriority "
294 "(priorityname, recpriority, selectclause) "
295 "VALUES(:NAME,:VALUE,:CLAUSE);");
296 query.bindValue(":NAME", m_titleEdit->GetText());
297 query.bindValue(":VALUE", item->GetText());
298 query.bindValue(":CLAUSE", m_descriptionEdit->GetText());
299
300 if (!query.exec())
301 MythDB::DBError("Install power search insert", query);
302 else
303 ScheduledRecording::ReschedulePlace("InstallCustomPriority");
304
305 Close();
306}
307
309{
310 if (!checkSyntax())
311 return;
312
314 query.prepare("DELETE FROM powerpriority "
315 "WHERE priorityname=:NAME;");
316 query.bindValue(":NAME", m_titleEdit->GetText());
317
318 if (!query.exec())
319 MythDB::DBError("Delete power search query", query);
320 else
321 ScheduledRecording::ReschedulePlace("DeleteCustomPriority");
322
323 Close();
324}
325
327{
328 bool ret = false;
329 QString msg;
330
331 QString desc = m_descriptionEdit->GetText();
332
333 if (desc.contains(RecordingInfo::kReLeadingAnd))
334 {
335 msg = "Power Priority rules do not reqiure a leading \"AND\"";
336 }
337 else if (desc.contains(';'))
338 {
339 msg = "Power Priority rules cannot include semicolon ( ; ) ";
340 msg += "statement terminators.";
341 }
342 else
343 {
344 QString qstr = QString("SELECT (%1) FROM (recordmatch, record, "
345 "program, channel, capturecard, "
346 "oldrecorded) WHERE NULL").arg(desc);
347 while (true)
348 {
349 int i = qstr.indexOf("RECTABLE");
350 if (i == -1) break;
351 qstr = qstr.replace(i, strlen("RECTABLE"), "record");
352 }
353
355 query.prepare(qstr);
356
357 if (query.exec())
358 {
359 ret = true;
360 }
361 else
362 {
363 msg = tr("An error was found when checking") + ":\n\n";
364 msg += query.executedQuery();
365 msg += "\n\n" + tr("The database error was") + ":\n";
366 msg += query.lastError().databaseText();
367 ret = false;
368 }
369 }
370
371 if (!msg.isEmpty())
372 ShowOkPopup(msg);
373
374 return ret;
375}
376
378{
380 if (!item)
381 return;
382
383 QString ttable = "powerpriority_tmp";
384
386 MSqlQuery query(dbcon);
387 QString thequery;
388
389 thequery = "SELECT GET_LOCK(:LOCK, 2);";
390 query.prepare(thequery);
391 query.bindValue(":LOCK", "DiffSchedule");
392 if (!query.exec())
393 {
394 QString msg =
395 QString("DB Error (Obtaining lock in testRecording): \n"
396 "Query was: %1 \nError was: %2 \n")
397 .arg(thequery,
399 LOG(VB_GENERAL, LOG_ERR, msg);
400 return;
401 }
402
403 thequery = QString("DROP TABLE IF EXISTS %1;").arg(ttable);
404 query.prepare(thequery);
405 if (!query.exec())
406 {
407 QString msg =
408 QString("DB Error (deleting old table in testRecording): \n"
409 "Query was: %1 \nError was: %2 \n")
410 .arg(thequery,
412 LOG(VB_GENERAL, LOG_ERR, msg);
413 return;
414 }
415
416 thequery = QString("CREATE TABLE %1 SELECT * FROM powerpriority;")
417 .arg(ttable);
418 query.prepare(thequery);
419 if (!query.exec())
420 {
421 QString msg =
422 QString("DB Error (create new table): \n"
423 "Query was: %1 \nError was: %2 \n")
424 .arg(thequery,
426 LOG(VB_GENERAL, LOG_ERR, msg);
427 return;
428 }
429
430 query.prepare(QString("DELETE FROM %1 WHERE priorityname = :NAME;")
431 .arg(ttable));
432 query.bindValue(":NAME", m_titleEdit->GetText());
433
434 if (!query.exec())
435 MythDB::DBError("Test power search delete", query);
436
437 thequery = QString("INSERT INTO %1 "
438 "(priorityname, recpriority, selectclause) "
439 "VALUES(:NAME,:VALUE,:CLAUSE);").arg(ttable);
440 query.prepare(thequery);
441 query.bindValue(":NAME", m_titleEdit->GetText());
442 query.bindValue(":VALUE", item->GetText());
443 query.bindValue(":CLAUSE", m_descriptionEdit->GetText());
444
445 if (!query.exec())
446 MythDB::DBError("Test power search insert", query);
447
448 QString ltitle = tr("Power Priority");
449 if (!m_titleEdit->GetText().isEmpty())
450 ltitle = m_titleEdit->GetText();
451
453 auto *vsd = new ViewScheduleDiff(mainStack, ttable, 0, ltitle);
454
455 if (vsd->Create())
456 mainStack->AddScreen(vsd);
457 else
458 delete vsd;
459
460 thequery = "SELECT RELEASE_LOCK(:LOCK);";
461 query.prepare(thequery);
462 query.bindValue(":LOCK", "DiffSchedule");
463 if (!query.exec())
464 {
465 QString msg =
466 QString("DB Error (free lock): \n"
467 "Query was: %1 \nError was: %2 \n")
468 .arg(thequery,
470 LOG(VB_GENERAL, LOG_ERR, msg);
471 }
472}
MythUIButton * m_cancelButton
MythUIButtonList * m_ruleList
MythUITextEdit * m_descriptionEdit
void testSchedule(void)
~CustomPriority() override
MythUIButton * m_deleteButton
void addClicked(void)
MythUIButton * m_testButton
void testClicked(void)
MythUIButton * m_installButton
MythUITextEdit * m_titleEdit
void deleteClicked(void)
MythUISpinBox * m_prioritySpin
MythUIButtonList * m_clauseList
void installClicked(void)
void ruleChanged(MythUIButtonListItem *item)
ProgramInfo * m_pginfo
bool checkSyntax(void)
CustomPriority(MythScreenStack *parent, ProgramInfo *proginfo=nullptr)
void loadExampleRules(void)
MythUIButton * m_addButton
bool Create() override
void loadData(void)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:128
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:838
QString executedQuery(void) const
Definition: mythdbcon.h:205
QSqlError lastError(void) const
Definition: mythdbcon.h:213
QVariant value(int i) const
Definition: mythdbcon.h:204
static MSqlQueryInfo SchedCon()
Returns dedicated connection. (Required for using temporary SQL tables.)
Definition: mythdbcon.cpp:581
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:619
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:889
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:813
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:551
static QString DBErrorMessage(const QSqlError &err)
Definition: mythdb.cpp:230
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
MythScreenStack * GetMainStack()
void addListener(QObject *listener)
Add a listener to the observable.
void removeListener(QObject *listener)
Remove a listener to the observable.
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual void Close()
QString GetText(const QString &name="") const
List widget, displays list items in a variety of themeable arrangements and can trigger signals when ...
MythUIButtonListItem * GetItemCurrent() const
void SetItemCurrent(MythUIButtonListItem *item)
int GetCurrentPos() const
void itemSelected(MythUIButtonListItem *item)
A single button widget.
Definition: mythuibutton.h:22
void Clicked()
A widget for offering a range of numerical values where only the the bounding values and interval are...
Definition: mythuispinbox.h:19
void SetRange(int low, int high, int step, uint pageMultiple=5)
Set the lower and upper bounds of the spinbox, the interval and page amount.
void SetValue(int val) override
Definition: mythuispinbox.h:28
A text entry and edit widget.
QString GetText(void) const
void SetText(const QString &text, bool moveCursor=true)
void SetMaxLength(int length)
void valueChanged()
void SetEnabled(bool enable)
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:129
Holds information on recordings and videos.
Definition: programinfo.h:74
QString GetTitle(void) const
Definition: programinfo.h:368
static const QRegularExpression kReLeadingAnd
static const QRegularExpression kReSearchTypeName
static void ReschedulePlace(const QString &why)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythConfirmationDialog * ShowOkPopup(const QString &message, bool showCancel)
Non-blocking version of MythPopupBox::showOkPopup()
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
@ COMM_DETECT_COMMFREE
Definition: programtypes.h:128
MSqlDatabase Info, used by MSqlQuery. Do not use directly.
Definition: mythdbcon.h:93
QString title
QString priority
QString description