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 QMapIterator<QString, QString> it(examples);
221 while (it.hasNext())
222 {
223 it.next();
225 QVariant::fromValue(it.value()));
226 }
227}
228
230{
231 if (!item)
232 return;
233
234 auto rule = item->GetData().value<RuleInfo>();
235
236 m_titleEdit->SetText(rule.title);
237
238 m_descriptionEdit->SetText(rule.description);
239 m_prioritySpin->SetValue(rule.priority);
241 textChanged();
242}
243
245{
246 bool hastitle = !m_titleEdit->GetText().isEmpty();
247 bool hasdesc = !m_descriptionEdit->GetText().isEmpty();
248
249 m_testButton->SetEnabled(hasdesc);
250 m_installButton->SetEnabled(hastitle && hasdesc);
251}
252
254{
256
257 if (!item)
258 return;
259
260 QString clause;
261
262 QString desc = m_descriptionEdit->GetText();
263
264 static const QRegularExpression kNonWhiteSpaceRE { "\\S" };
265 if (desc.contains(kNonWhiteSpaceRE))
266 clause = "AND ";
267 clause += item->GetData().toString();
268 m_descriptionEdit->SetText(desc.append(clause));
269}
270
272{
273 if (!checkSyntax())
274 return;
275
276 testSchedule();
277}
278
280{
281 if (!checkSyntax())
282 return;
283
285 if (!item)
286 return;
287
289 query.prepare("DELETE FROM powerpriority WHERE priorityname = :NAME;");
290 query.bindValue(":NAME", m_titleEdit->GetText());
291
292 if (!query.exec())
293 MythDB::DBError("Install power search delete", query);
294
295 query.prepare("INSERT INTO powerpriority "
296 "(priorityname, recpriority, selectclause) "
297 "VALUES(:NAME,:VALUE,:CLAUSE);");
298 query.bindValue(":NAME", m_titleEdit->GetText());
299 query.bindValue(":VALUE", item->GetText());
300 query.bindValue(":CLAUSE", m_descriptionEdit->GetText());
301
302 if (!query.exec())
303 MythDB::DBError("Install power search insert", query);
304 else
305 ScheduledRecording::ReschedulePlace("InstallCustomPriority");
306
307 Close();
308}
309
311{
312 if (!checkSyntax())
313 return;
314
316 query.prepare("DELETE FROM powerpriority "
317 "WHERE priorityname=:NAME;");
318 query.bindValue(":NAME", m_titleEdit->GetText());
319
320 if (!query.exec())
321 MythDB::DBError("Delete power search query", query);
322 else
323 ScheduledRecording::ReschedulePlace("DeleteCustomPriority");
324
325 Close();
326}
327
329{
330 bool ret = false;
331 QString msg;
332
333 QString desc = m_descriptionEdit->GetText();
334
335 if (desc.contains(RecordingInfo::kReLeadingAnd))
336 {
337 msg = "Power Priority rules do not reqiure a leading \"AND\"";
338 }
339 else if (desc.contains(';'))
340 {
341 msg = "Power Priority rules cannot include semicolon ( ; ) ";
342 msg += "statement terminators.";
343 }
344 else
345 {
346 QString qstr = QString("SELECT (%1) FROM (recordmatch, record, "
347 "program, channel, capturecard, "
348 "oldrecorded) WHERE NULL").arg(desc);
349 while (true)
350 {
351 int i = qstr.indexOf("RECTABLE");
352 if (i == -1) break;
353 qstr = qstr.replace(i, strlen("RECTABLE"), "record");
354 }
355
357 query.prepare(qstr);
358
359 if (query.exec())
360 {
361 ret = true;
362 }
363 else
364 {
365 msg = tr("An error was found when checking") + ":\n\n";
366 msg += query.executedQuery();
367 msg += "\n\n" + tr("The database error was") + ":\n";
368 msg += query.lastError().databaseText();
369 ret = false;
370 }
371 }
372
373 if (!msg.isEmpty())
374 ShowOkPopup(msg);
375
376 return ret;
377}
378
380{
382 if (!item)
383 return;
384
385 QString ttable = "powerpriority_tmp";
386
388 MSqlQuery query(dbcon);
389 QString thequery;
390
391 thequery = "SELECT GET_LOCK(:LOCK, 2);";
392 query.prepare(thequery);
393 query.bindValue(":LOCK", "DiffSchedule");
394 if (!query.exec())
395 {
396 QString msg =
397 QString("DB Error (Obtaining lock in testRecording): \n"
398 "Query was: %1 \nError was: %2 \n")
399 .arg(thequery,
401 LOG(VB_GENERAL, LOG_ERR, msg);
402 return;
403 }
404
405 thequery = QString("DROP TABLE IF EXISTS %1;").arg(ttable);
406 query.prepare(thequery);
407 if (!query.exec())
408 {
409 QString msg =
410 QString("DB Error (deleting old table in testRecording): \n"
411 "Query was: %1 \nError was: %2 \n")
412 .arg(thequery,
414 LOG(VB_GENERAL, LOG_ERR, msg);
415 return;
416 }
417
418 thequery = QString("CREATE TABLE %1 SELECT * FROM powerpriority;")
419 .arg(ttable);
420 query.prepare(thequery);
421 if (!query.exec())
422 {
423 QString msg =
424 QString("DB Error (create new table): \n"
425 "Query was: %1 \nError was: %2 \n")
426 .arg(thequery,
428 LOG(VB_GENERAL, LOG_ERR, msg);
429 return;
430 }
431
432 query.prepare(QString("DELETE FROM %1 WHERE priorityname = :NAME;")
433 .arg(ttable));
434 query.bindValue(":NAME", m_titleEdit->GetText());
435
436 if (!query.exec())
437 MythDB::DBError("Test power search delete", query);
438
439 thequery = QString("INSERT INTO %1 "
440 "(priorityname, recpriority, selectclause) "
441 "VALUES(:NAME,:VALUE,:CLAUSE);").arg(ttable);
442 query.prepare(thequery);
443 query.bindValue(":NAME", m_titleEdit->GetText());
444 query.bindValue(":VALUE", item->GetText());
445 query.bindValue(":CLAUSE", m_descriptionEdit->GetText());
446
447 if (!query.exec())
448 MythDB::DBError("Test power search insert", query);
449
450 QString ltitle = tr("Power Priority");
451 if (!m_titleEdit->GetText().isEmpty())
452 ltitle = m_titleEdit->GetText();
453
455 auto *vsd = new ViewScheduleDiff(mainStack, ttable, 0, ltitle);
456
457 if (vsd->Create())
458 mainStack->AddScreen(vsd);
459 else
460 delete vsd;
461
462 thequery = "SELECT RELEASE_LOCK(:LOCK);";
463 query.prepare(thequery);
464 query.bindValue(":LOCK", "DiffSchedule");
465 if (!query.exec())
466 {
467 QString msg =
468 QString("DB Error (free lock): \n"
469 "Query was: %1 \nError was: %2 \n")
470 .arg(thequery,
472 LOG(VB_GENERAL, LOG_ERR, msg);
473 }
474}
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:837
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:580
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:618
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:888
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:812
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:550
static QString DBErrorMessage(const QSqlError &err)
Definition: mythdb.cpp:231
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:226
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:17
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:26
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:138
Holds information on recordings and videos.
Definition: programinfo.h:68
QString GetTitle(void) const
Definition: programinfo.h:362
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