MythTV master
channelgroup.cpp
Go to the documentation of this file.
1// c++
2#include <algorithm>
3
4// mythtv
7
8#include "channelgroup.h"
9#include "channelutil.h"
10#include "sourceutil.h"
11
12#define LOC QString("Channel Group: ")
13
14inline bool lt_group(const ChannelGroupItem &a, const ChannelGroupItem &b)
15{
16 return QString::localeAwareCompare(a.m_name, b.m_name) < 0;
17}
18
19bool ChannelGroup::ToggleChannel(uint chanid, int changrpid, bool delete_chan)
20{
21 // Check if it already exists for that chanid...
23 query.prepare(
24 "SELECT channelgroup.id "
25 "FROM channelgroup "
26 "WHERE channelgroup.chanid = :CHANID AND "
27 "channelgroup.grpid = :GRPID "
28 "LIMIT 1");
29 query.bindValue(":CHANID", chanid);
30 query.bindValue(":GRPID", changrpid);
31
32 if (!query.exec())
33 {
34 MythDB::DBError("ChannelGroup::ToggleChannel", query);
35 return false;
36 }
37 if (query.next() && delete_chan)
38 {
39 // We have a record...Remove it to toggle...
40 QString id = query.value(0).toString();
41 query.prepare("DELETE FROM channelgroup WHERE id = :CHANID");
42 query.bindValue(":CHANID", id);
43 if (!query.exec())
44 MythDB::DBError("ChannelGroup::ToggleChannel -- delete", query);
45 LOG(VB_GENERAL, LOG_INFO, LOC +
46 QString("Removing channel %1 from group %2.").arg(id).arg(changrpid));
47 }
48 else if (query.size() == 0)
49 {
50 // We have no record...Add one to toggle...
51 query.prepare("INSERT INTO channelgroup (chanid,grpid) "
52 "VALUES (:CHANID, :GRPID)");
53 query.bindValue(":CHANID", chanid);
54 query.bindValue(":GRPID", changrpid);
55 if (!query.exec())
56 MythDB::DBError("ChannelGroup::ToggleChannel -- insert", query);
57 LOG(VB_GENERAL, LOG_INFO, LOC +
58 QString("Adding channel %1 to group %2.")
59 .arg(chanid).arg(changrpid));
60 }
61 else
62 {
63 LOG(VB_GENERAL, LOG_INFO, LOC +
64 QString("Channel %1 already present in group %2.")
65 .arg(chanid).arg(changrpid));
66 }
67
68 return true;
69}
70
71bool ChannelGroup::AddChannel(uint chanid, int changrpid)
72{
73 // Make sure the channel group exists
75 query.prepare(
76 "SELECT grpid, name FROM channelgroupnames "
77 "WHERE grpid = :GRPID");
78 query.bindValue(":GRPID", changrpid);
79
80 if (!query.exec())
81 {
82 MythDB::DBError("ChannelGroup::AddChannel", query);
83 return false;
84 }
85 if (query.size() == 0)
86 {
87 LOG(VB_GENERAL, LOG_INFO, LOC +
88 QString("AddChannel failed to find channel group %1.").arg(changrpid));
89 return false;
90 }
91
92 query.first();
93 QString groupName = query.value(1).toString();
94
95 // Make sure the channel exists and is visible
96 query.prepare(
97 "SELECT chanid, name FROM channel "
98 "WHERE chanid = :CHANID "
99 "AND visible > 0 "
100 "AND deleted IS NULL");
101 query.bindValue(":CHANID", chanid);
102
103 if (!query.exec())
104 {
105 MythDB::DBError("ChannelGroup::AddChannel", query);
106 return false;
107 }
108 if (query.size() == 0)
109 {
110 LOG(VB_GENERAL, LOG_INFO, LOC +
111 QString("AddChannel failed to find channel %1.").arg(chanid));
112 return false;
113 }
114
115 query.first();
116 QString chanName = query.value(1).toString();
117
118 // Check if it already exists for that chanid...
119 query.prepare(
120 "SELECT channelgroup.id "
121 "FROM channelgroup "
122 "WHERE channelgroup.chanid = :CHANID AND "
123 "channelgroup.grpid = :GRPID "
124 "LIMIT 1");
125 query.bindValue(":CHANID", chanid);
126 query.bindValue(":GRPID", changrpid);
127
128 if (!query.exec())
129 {
130 MythDB::DBError("ChannelGroup::AddChannel", query);
131 return false;
132 }
133 if (query.size() == 0)
134 {
135 // We have no record...Add one to toggle...
136 query.prepare("INSERT INTO channelgroup (chanid,grpid) "
137 "VALUES (:CHANID, :GRPID)");
138 query.bindValue(":CHANID", chanid);
139 query.bindValue(":GRPID", changrpid);
140 if (!query.exec())
141 MythDB::DBError("ChannelGroup::AddChannel -- insert", query);
142 LOG(VB_GENERAL, LOG_INFO, LOC +
143 QString("Adding channel %1 to group %2.")
144 .arg(chanName, groupName));
145 }
146
147 return true;
148}
149
150bool ChannelGroup::DeleteChannel(uint chanid, int changrpid)
151{
152 // Check if it already exists for that chanid...
154 query.prepare(
155 "SELECT channelgroup.id "
156 "FROM channelgroup "
157 "WHERE channelgroup.chanid = :CHANID AND "
158 "channelgroup.grpid = :GRPID "
159 "LIMIT 1");
160 query.bindValue(":CHANID", chanid);
161 query.bindValue(":GRPID", changrpid);
162
163 if (!query.exec())
164 {
165 MythDB::DBError("ChannelGroup::DeleteChannel", query);
166 return false;
167 }
168 if (query.next())
169 {
170 // We have a record...Remove it to toggle...
171 QString id = query.value(0).toString();
172 query.prepare("DELETE FROM channelgroup WHERE id = :CHANID");
173 query.bindValue(":CHANID", id);
174 if (!query.exec())
175 MythDB::DBError("ChannelGroup::DeleteChannel -- delete", query);
176 LOG(VB_GENERAL, LOG_INFO, LOC +
177 QString("Removing channel with id=%1.").arg(id));
178 }
179
180 return true;
181}
182
183// Create a list of all channel groups that are manually configured
184//
185// The channel groups are returned in the following order:
186// - Favorites
187// - Manually created channel groups, sorted by name.
188//
190{
191 ChannelGroupList list;
192 QString qstr;
194
195 // Always the Favorites with group id 1
196 if (includeEmpty)
197 {
198 qstr = "SELECT grpid, name FROM channelgroupnames"
199 " WHERE grpid = 1";
200 }
201 else
202 {
203 qstr = "SELECT DISTINCT t1.grpid, name FROM channelgroupnames t1, channelgroup t2"
204 " WHERE t1.grpid = t2.grpid"
205 " AND t1.grpid = 1";
206 }
207 query.prepare(qstr);
208 if (!query.exec())
209 {
210 MythDB::DBError("ChannelGroup::GetChannelGroups Favorites", query);
211 }
212 else
213 {
214 if (query.next())
215 {
216 ChannelGroupItem group(query.value(0).toUInt(),
217 query.value(1).toString());
218 list.push_back(group);
219 }
220 }
221
222 // Then the channel groups that are manually created
223 if (includeEmpty)
224 {
225 qstr = "SELECT grpid, name FROM channelgroupnames"
226 " WHERE name NOT IN (SELECT name FROM videosource)"
227 " AND name <> 'Priority' "
228 " AND grpid <> 1"
229 " ORDER BY NAME";
230 }
231 else
232 {
233 qstr = "SELECT DISTINCT t1.grpid, name FROM channelgroupnames t1, channelgroup t2"
234 " WHERE t1.grpid = t2.grpid"
235 " AND name NOT IN (SELECT name FROM videosource)"
236 " AND name <> 'Priority' "
237 " AND t1.grpid <> 1"
238 " ORDER BY NAME";
239 }
240 query.prepare(qstr);
241 if (!query.exec())
242 {
243 MythDB::DBError("ChannelGroup::GetChannelGroups manual", query);
244 }
245 else
246 {
247 while (query.next())
248 {
249 ChannelGroupItem group(query.value(0).toUInt(),
250 query.value(1).toString());
251 list.push_back(group);
252 }
253 }
254
255 return list;
256}
257
258// Create a list of all channel groups that are automatically created
259//
260// The channel groups are returned in the following order:
261// - Priority channels
262// - Channel groups created automatically from videosources, sorted by name.
263//
265{
266 ChannelGroupList list;
267 QString qstr;
269
270 // The Priority channel group if it exists
271 if (includeEmpty)
272 {
273 qstr = "SELECT grpid, name FROM channelgroupnames"
274 " WHERE name = 'Priority'";
275 }
276 else
277 {
278 qstr = "SELECT DISTINCT t1.grpid, name FROM channelgroupnames t1, channelgroup t2"
279 " WHERE t1.grpid = t2.grpid "
280 " AND name = 'Priority'";
281 }
282 query.prepare(qstr);
283 if (!query.exec())
284 {
285 MythDB::DBError("ChannelGroup::GetChannelGroups Priority", query);
286 }
287 else
288 {
289 if (query.next())
290 {
291 ChannelGroupItem group(query.value(0).toUInt(),
292 query.value(1).toString());
293 list.push_back(group);
294 }
295 }
296
297 // The channel groups that are automatically created from videosources
298 if (includeEmpty)
299 {
300 qstr = "SELECT grpid, name FROM channelgroupnames"
301 " WHERE name IN (SELECT name FROM videosource)"
302 " ORDER BY NAME";
303 }
304 else
305 {
306 qstr = "SELECT DISTINCT t1.grpid, name FROM channelgroupnames t1, channelgroup t2"
307 " WHERE t1.grpid = t2.grpid"
308 " AND name IN (SELECT name FROM videosource)"
309 " ORDER BY NAME";
310 }
311 query.prepare(qstr);
312 if (!query.exec())
313 {
314 MythDB::DBError("ChannelGroup::GetChannelGroups videosources", query);
315 }
316 else
317 {
318 while (query.next())
319 {
320 ChannelGroupItem group(query.value(0).toUInt(),
321 query.value(1).toString());
322 list.push_back(group);
323 }
324 }
325 return list;
326}
327
328// Create a list of all channel groups
329//
330// The channel groups are returned in the following order:
331// - Favorites
332// - Manually created channel groups, sorted by name.
333// - Priority channels
334// - Channel groups created automatically from videosources, sorted by name.
335//
337{
338 ChannelGroupList list = GetManualChannelGroups(includeEmpty);
339 ChannelGroupList more = GetAutomaticChannelGroups(includeEmpty);
340 list.insert(list.end(), more.begin(), more.end());
341 return list;
342}
343
344// Cycle through the available channel groups.
345// At the end return -1 to select "All Channels".
347{
348 // If no groups return -1 for "All Channels"
349 if (sorted.empty())
350 return -1;
351
352 // If grpid is "All Channels" (-1), then return the first grpid
353 if (grpid == -1)
354 return sorted[0].m_grpId;
355
356 auto it = std::ranges::find(sorted, grpid, &ChannelGroupItem::m_grpId);
357
358 // If grpid is not in the list, return -1 for "All Channels"
359 if (it == sorted.end())
360 return -1;
361
362 ++it;
363
364 // If we reached the end, the next option is "All Channels" (-1)
365 if (it == sorted.end())
366 return -1;
367
368 return it->m_grpId;
369}
370
371bool ChannelGroup::InChannelGroupList(const ChannelGroupList &groupList, int grpid)
372{
373 auto it = std::ranges::find(groupList, grpid, &ChannelGroupItem::m_grpId);
374 return it != groupList.end();
375}
376
378{
379 return !InChannelGroupList(groupList, grpid);
380}
381
383{
384 // All Channels
385 if (grpid == -1)
386 return tr("All Channels");
387
388 // No group
389 if (grpid == 0)
390 return "";
391
393 query.prepare("SELECT name FROM channelgroupnames WHERE grpid = :GROUPID");
394 query.bindValue(":GROUPID", grpid);
395
396 if (!query.exec())
397 MythDB::DBError("ChannelGroup::GetChannelGroups", query);
398 else if (query.next())
399 return query.value(0).toString();
400
401 return "";
402}
403
404int ChannelGroup::GetChannelGroupId(const QString& changroupname)
405{
406 // All Channels
407 if (changroupname == "All Channels")
408 return -1;
409
411
412 query.prepare("SELECT grpid FROM channelgroupnames "
413 "WHERE name = :GROUPNAME");
414 query.bindValue(":GROUPNAME", changroupname);
415
416 if (!query.exec())
417 MythDB::DBError("ChannelGroup::GetChannelGroups", query);
418 else if (query.next())
419 return query.value(0).toUInt();
420
421 return 0;
422}
423
424int ChannelGroup::AddChannelGroup(const QString &groupName)
425{
426 int groupId = ChannelGroup::GetChannelGroupId(groupName);
427 if (groupId == 0)
428 {
429 LOG(VB_GENERAL, LOG_INFO, QString("Add channelgroup %1").arg(groupName));
430
432 query.prepare("INSERT INTO channelgroupnames (name) VALUE (:NEWNAME);");
433 query.bindValue(":NEWNAME", groupName);
434
435 if (!query.exec())
436 MythDB::DBError("AddChannelGroup", query);
437 groupId = query.lastInsertId().toInt();
438 }
439 return groupId;
440}
441
442bool ChannelGroup::RemoveChannelGroup(const QString &groupName)
443{
444 int groupId = ChannelGroup::GetChannelGroupId(groupName);
445 if (groupId > 0)
446 {
447 // Yes, channelgroup does exist. Remove all existing channels.
448 LOG(VB_GENERAL, LOG_INFO, QString("Remove channels of channelgroup %1").arg(groupName));
449
451 query.prepare("DELETE FROM channelgroup WHERE grpid = :GRPID;");
452 query.bindValue(":GRPID", groupId);
453
454 if (!query.exec())
455 MythDB::DBError("RemoveChannelGroup 1", query);
456
457 // And also the channelgroupname
458 LOG(VB_GENERAL, LOG_INFO, QString("Remove channelgroup %1").arg(groupName));
459 query.prepare("DELETE FROM channelgroupnames WHERE grpid = :GRPID;");
460 query.bindValue(":GRPID", groupId);
461
462 if (!query.exec())
463 MythDB::DBError("RemoveChannelGroup 2", query);
464 }
465 else
466 {
467 LOG(VB_GENERAL, LOG_DEBUG, QString("Channelgroup %1 not found").arg(groupName));
468 return false;
469 }
470 return true;
471}
472
473bool ChannelGroup::UpdateChannelGroup(const QString & oldName, const QString & newName)
474{
475 // Check if new name already exists
476 int groupId = ChannelGroup::GetChannelGroupId(newName);
477 if (groupId > 0)
478 return false;
479
481 QString qstr = "UPDATE channelgroupnames set name = :NEWNAME "
482 " WHERE name = :OLDNAME ;";
483 query.prepare(qstr);
484 query.bindValue(":NEWNAME", newName);
485 query.bindValue(":OLDNAME", oldName);
486
487 if (!query.exec())
488 {
489 MythDB::DBError("ChannelGroup::UpdateChannelGroup fail", query);
490 return false;
491 }
492 return true;
493}
494
495// UpdateChannelGroups
496//
497// Create and maintain a channel group for each connected video source
498// with the name of the video source.
499// Create and maintain a channel group Priority for
500// all channels that have a recording priority bigger than 0.
501//
503{
504 QMap<int, QString> allSources;
505 QMap<int, QString> connectedSources;
506 QMap<int, QString> disconnectedSources;
507
508 LOG(VB_GENERAL, LOG_INFO, QString("Running UpdateChannelGroups"));
509
510 // Get list of all video sources
511 {
513 query.prepare("SELECT sourceid,name FROM videosource;");
514 if (query.exec())
515 {
516 while (query.next())
517 allSources[query.value(0).toInt()] = query.value(1).toString();
518 }
519 else
520 {
521 MythDB::DBError("UpdateChannelGroups videosource 1", query);
522 }
523 }
524
525 // Split all video sources into a list of video sources that are connected to one
526 // or more capture cards and a list of video sources that are not connected.
527 for (auto it = allSources.cbegin(); it != allSources.cend(); ++it)
528 {
529 uint sourceId = it.key();
530 int count = SourceUtil::GetConnectionCount(sourceId);
531 if (count > 0)
532 connectedSources[sourceId] = allSources[sourceId];
533 else
534 disconnectedSources[sourceId] = allSources[sourceId];
535 }
536
537 // If there is only one connected video source then we do not need a special
538 // channel group for that video source; it is then the same as "All Channels".
539 QMap<int, QString> removeSources = disconnectedSources;
540 if (connectedSources.size() == 1)
541 {
542 auto it = connectedSources.cbegin();
543 uint sourceid = it.key();
544 removeSources[sourceid] = *it;
545 }
546
547 // Remove channelgroup channels and the channelgroupname for disconnected video sources.
548 for (const auto &sourceName : std::as_const(removeSources))
549 {
550 RemoveChannelGroup(sourceName);
551 }
552
553 // Remove all channels that do not exist anymore or that are not visible.
554 // This is done only for the automatic channel groups.
555 {
558 for (const auto &chgrp : list)
559 {
560 query.prepare(
561 "DELETE from channelgroup WHERE grpid = :GRPID"
562 " AND chanid NOT IN "
563 " (SELECT chanid FROM channel WHERE deleted IS NULL AND visible > 0)");
564 query.bindValue(":GRPID", chgrp.m_grpId);
565 if (!query.exec())
566 {
567 MythDB::DBError("ChannelGroup::UpdateChannelGroups", query);
568 return;
569 }
570 if (query.numRowsAffected() > 0)
571 {
572 LOG(VB_GENERAL, LOG_INFO, QString("Removed %1 channels from channelgroup %2")
573 .arg(query.numRowsAffected()).arg(chgrp.m_name));
574 }
575 }
576 }
577
578 // Create a channel group for each connected video source only if there is
579 // more than one video source configured with a capture card.
580 if (connectedSources.size() > 1)
581 {
582 // Add channelgroupname entry if it does not exist yet
583 for (const auto &sourceName : std::as_const(connectedSources))
584 {
585 AddChannelGroup(sourceName);
586 }
587
588 // Add all visible channels to the channelgroups
589 for (auto it = connectedSources.cbegin(); it != connectedSources.cend(); ++it)
590 {
591 uint sourceId = it.key();
592 QString sourceName = connectedSources[sourceId];
593 int groupId = ChannelGroup::GetChannelGroupId(sourceName);
594
595 if (groupId > 0)
596 {
597 LOG(VB_GENERAL, LOG_INFO, QString("Update channelgroup %1").arg(sourceName));
599 query.prepare(
600 "SELECT chanid FROM channel "
601 "WHERE sourceid = :SOURCEID "
602 "AND deleted IS NULL "
603 "AND visible > 0 ");
604 query.bindValue(":SOURCEID", sourceId);
605 if (!query.exec())
606 {
607 MythDB::DBError("ChannelGroup::UpdateChannelGroups", query);
608 return;
609 }
610 while (query.next())
611 {
612 uint chanId = query.value(0).toUInt();
613 ChannelGroup::AddChannel(chanId, groupId);
614 }
615 }
616 }
617 }
618
619 // Channelgroup Priority
620
621 // Find the number of priority channels in all connected video sources
622 uint numPrioChannels = 0;
623 for (auto it = connectedSources.cbegin(); it != connectedSources.cend(); ++it)
624 {
625 uint sourceId = it.key();
627 query.prepare(
628 "SELECT count(*) FROM channel "
629 "WHERE sourceid = :SOURCEID "
630 "AND deleted IS NULL "
631 "AND visible > 0 "
632 "AND recpriority > 0");
633 query.bindValue(":SOURCEID", sourceId);
634 if (!query.exec())
635 {
636 MythDB::DBError("UpdateChannelGroups Priority select channels", query);
637 return;
638 }
639 if (query.next())
640 {
641 numPrioChannels += query.value(0).toUInt();
642 }
643 }
644 LOG(VB_GENERAL, LOG_INFO, QString("Found %1 priority channels").arg(numPrioChannels));
645
646 if (numPrioChannels > 0)
647 {
648 // Add channel group for Priority channels
649 QString groupName = "Priority";
650 AddChannelGroup(groupName);
651 LOG(VB_GENERAL, LOG_INFO, QString("Update channelgroup %1").arg(groupName));
652
653 // Update all channels in channel group Priority
654 int groupId = ChannelGroup::GetChannelGroupId(groupName);
655 if (groupId > 0)
656 {
657 // Remove all channels from channelgroup Priority that do not have priority anymore.
659 query.prepare(
660 "DELETE from channelgroup WHERE grpid = :GRPID "
661 " AND chanid NOT IN "
662 " (SELECT chanid FROM channel "
663 " WHERE deleted IS NULL "
664 " AND visible > 0 "
665 " AND recpriority > 0)");
666 query.bindValue(":GRPID", groupId);
667 if (!query.exec())
668 {
669 MythDB::DBError("ChannelGroup::UpdateChannelGroups", query);
670 return;
671 }
672 if (query.numRowsAffected() > 0)
673 {
674 LOG(VB_GENERAL, LOG_INFO, QString("Removed %1 channels from channelgroup Priority")
675 .arg(query.numRowsAffected()));
676 }
677
678 // Add all channels from all connected video groups if they have priority
679 for (auto it = connectedSources.cbegin(); it != connectedSources.cend(); ++it)
680 {
681 uint sourceId = it.key();
682 query.prepare(
683 "SELECT chanid FROM channel "
684 "WHERE sourceid = :SOURCEID "
685 "AND deleted IS NULL "
686 "AND visible > 0 "
687 "AND recpriority > 0");
688 query.bindValue(":SOURCEID", sourceId);
689 if (!query.exec())
690 {
691 MythDB::DBError("UpdateChannelGroups Priority select channels", query);
692 return;
693 }
694 while (query.next())
695 {
696 uint chanId = query.value(0).toUInt();
697 ChannelGroup::AddChannel(chanId, groupId);
698 }
699 }
700 }
701 }
702 else
703 {
704 // No priority channels in connected video sources, so no Priority channel group
705 RemoveChannelGroup("Priority");
706 }
707}
#define LOC
bool lt_group(const ChannelGroupItem &a, const ChannelGroupItem &b)
std::vector< ChannelGroupItem > ChannelGroupList
Definition: channelgroup.h:31
static bool NotInChannelGroupList(const ChannelGroupList &groupList, int grpid)
static bool InChannelGroupList(const ChannelGroupList &groupList, int grpid)
static ChannelGroupList GetAutomaticChannelGroups(bool includeEmpty=true)
static bool RemoveChannelGroup(const QString &groupName)
static QString GetChannelGroupName(int grpid)
static void UpdateChannelGroups(void)
static bool AddChannel(uint chanid, int changrpid)
static int GetNextChannelGroup(const ChannelGroupList &sorted, int grpid)
static ChannelGroupList GetChannelGroups(bool includeEmpty=true)
static bool DeleteChannel(uint chanid, int changrpid)
static bool ToggleChannel(uint chanid, int changrpid, bool delete_chan)
static ChannelGroupList GetManualChannelGroups(bool includeEmpty=true)
static int GetChannelGroupId(const QString &changroupname)
static int AddChannelGroup(const QString &groupName)
static bool UpdateChannelGroup(const QString &oldName, const QString &newName)
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:129
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:839
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:824
QVariant value(int i) const
Definition: mythdbcon.h:205
int size(void) const
Definition: mythdbcon.h:215
int numRowsAffected() const
Definition: mythdbcon.h:218
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:620
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:890
QVariant lastInsertId()
Return the id of the last inserted row.
Definition: mythdbcon.cpp:937
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:814
static MSqlQueryInfo InitCon(ConnectionReuse _reuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:552
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:225
static uint GetConnectionCount(uint sourceid)
Definition: sourceutil.cpp:235
unsigned int uint
Definition: compat.h:60
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39