Exemple #1
0
	/*
	 * Loads existing indexes from disk
	 */
	void IndexManager::loadIndexes(SimpleXML& xml)
	{
		xml.resetCurrentChild();
		if(xml.findChild("Files"))
		{
			xml.stepIn();
			while(xml.findChild("File"))
			{
				const TTHValue tth = TTHValue(xml.getChildAttrib("TTH"));
				SourceList sources;

				xml.stepIn();
				while(xml.findChild("Source"))
				{
					Source source;
					source.setCID(CID(xml.getChildAttrib("CID")));
					source.setIp(xml.getChildAttrib("I4"));
					source.setUdpPort(static_cast<uint16_t>(xml.getIntChildAttrib("U4")));
					source.setSize(xml.getLongLongChildAttrib("SI"));
					source.setExpires(xml.getLongLongChildAttrib("EX"));
					source.setPartial(false);

					sources.push_back(source);
				}

				tthList.insert(std::make_pair(tth, sources));
				xml.stepOut();
			}
			xml.stepOut();
		}
	}
Exemple #2
0
CClientList::SourceList	CClientList::GetClientsByIP( unsigned long ip )
{
	SourceList results;

	// Find all items with the specified hash
	std::pair<IDMap::iterator, IDMap::iterator> range = m_ipList.equal_range( ip );

	for ( ; range.first != range.second; range.first++ )  {
		results.push_back( range.first->second );
	}

	return results;
}
Exemple #3
0
CClientList::SourceList	CClientList::GetClientsByHash( const CMD4Hash& hash )
{
	SourceList results;

	// Find all items with the specified hash
	std::pair<HashMap::iterator, HashMap::iterator> range = m_hashList.equal_range( hash );

	for ( ; range.first != range.second; ++range.first)  {
		results.push_back( range.first->second );
	}

	return results;
}
Exemple #4
0
int main(int argc, char *argv[])
{
    FillData fill_data;
    int fromfile_id = 1;
    int fromfile_offset = 0;
    QString fromfile_name;
    bool from_file = false;
    bool mark_repeats = true;

    bool usingDataDirect = false;

    bool from_dd_file = false;
    int sourceid = -1;
    QString fromddfile_lineupid;

    MythFillDatabaseCommandLineParser cmdline;
    if (!cmdline.Parse(argc, argv))
    {
        cmdline.PrintHelp();
        return GENERIC_EXIT_INVALID_CMDLINE;
    }

    if (cmdline.toBool("showhelp"))
    {
        cmdline.PrintHelp();
        return GENERIC_EXIT_OK;
    }

    if (cmdline.toBool("showversion"))
    {
        cmdline.PrintVersion();
        return GENERIC_EXIT_OK;
    }

    QCoreApplication a(argc, argv);
    QCoreApplication::setApplicationName(MYTH_APPNAME_MYTHFILLDATABASE);

    myth_nice(19);

    int retval;
    if ((retval = cmdline.ConfigureLogging()) != GENERIC_EXIT_OK)
        return retval;

    if (cmdline.toBool("manual"))
    {
        cout << "###\n";
        cout << "### Running in manual channel configuration mode.\n";
        cout << "### This will ask you questions about every channel.\n";
        cout << "###\n";
        fill_data.chan_data.m_interactive = true;
    }

    if (cmdline.toBool("onlyguide") || cmdline.toBool("update"))
    {
        LOG(VB_GENERAL, LOG_NOTICE,
            "Only updating guide data, channel and icon updates will be ignored");
        fill_data.chan_data.m_guideDataOnly = true;
    }

    if (cmdline.toBool("preset"))
    {
        cout << "###\n";
        cout << "### Running in preset channel configuration mode.\n";
        cout << "### This will assign channel ";
        cout << "preset numbers to every channel.\n";
        cout << "###\n";
        fill_data.chan_data.m_channelPreset = true;
    }

    if (cmdline.toBool("file"))
    {
        // manual file mode
        if (!cmdline.toBool("sourceid") ||
            !cmdline.toBool("xmlfile"))
        {
            cerr << "The --file option must be used in combination" << endl
                 << "with both --sourceid and --xmlfile." << endl;
            return GENERIC_EXIT_INVALID_CMDLINE;
        }

        fromfile_id = cmdline.toInt("sourceid");
        fromfile_name = cmdline.toString("xmlfile");

        LOG(VB_GENERAL, LOG_INFO,
            "Bypassing grabbers, reading directly from file");
        from_file = true;
    }

    if (cmdline.toBool("ddfile"))
    {
        // datadirect file mode
        if (!cmdline.toBool("sourceid") || 
            !cmdline.toBool("offset") ||
            !cmdline.toBool("lineupid") ||
            !cmdline.toBool("xmlfile"))
        {
            cerr << "The --dd-file option must be used in combination" << endl
                 << "with each of --sourceid, --offset, --lineupid," << endl
                 << "and --xmlfile." << endl;
            return GENERIC_EXIT_INVALID_CMDLINE;
        }

        fromfile_id         = cmdline.toInt("sourceid");
        fromfile_offset     = cmdline.toInt("offset");
        fromddfile_lineupid = cmdline.toString("lineupid");
        fromfile_name       = cmdline.toString("xmlfile");

        LOG(VB_GENERAL, LOG_INFO,
            "Bypassing grabbers, reading directly from file");
        from_dd_file = true;
    }

    if (cmdline.toBool("dochannelupdates"))
        fill_data.chan_data.m_channelUpdates = true;
    if (cmdline.toBool("removechannels"))
        fill_data.chan_data.m_removeNewChannels = true;
    if (cmdline.toBool("nofilterchannels"))
        fill_data.chan_data.m_filterNewChannels = false;
    if (!cmdline.GetPassthrough().isEmpty())
        fill_data.graboptions = " " + cmdline.GetPassthrough();
    if (cmdline.toBool("sourceid"))
        sourceid = cmdline.toInt("sourceid");
    if (cmdline.toBool("cardtype"))
    {
        if (!cmdline.toBool("sourceid"))
        {
            cerr << "The --cardtype option must be used in combination" << endl
                 << "with a --sourceid option." << endl;
            return GENERIC_EXIT_INVALID_CMDLINE;
        }

        fill_data.chan_data.m_cardType = cmdline.toString("cardtype")
                                                .trimmed().toUpper();
    }
    if (cmdline.toBool("maxdays") && cmdline.toInt("maxdays") > 0)
    {
        fill_data.maxDays = cmdline.toInt("maxdays");
        if (fill_data.maxDays == 1)
            fill_data.SetRefresh(0, true);
    }

    if (cmdline.toBool("refreshtoday"))
        cmdline.SetValue("refresh",
                cmdline.toStringList("refresh") << "today");
    if (cmdline.toBool("dontrefreshtomorrow"))
        cmdline.SetValue("refresh", 
                cmdline.toStringList("refresh") << "nottomorrow");
    if (cmdline.toBool("refreshsecond"))
        cmdline.SetValue("refresh", 
                cmdline.toStringList("refresh") << "second");
    if (cmdline.toBool("refreshall"))
        cmdline.SetValue("refresh", 
                cmdline.toStringList("refresh") << "all");
    if (cmdline.toBool("refreshday"))
        cmdline.SetValue("refresh",
                cmdline.toStringList("refresh") << 
                                        cmdline.toStringList("refreshday"));

    QStringList sl = cmdline.toStringList("refresh");
    if (!sl.isEmpty())
    {
        QStringList::const_iterator i = sl.constBegin();
        for (; i != sl.constEnd(); ++i)
        {
            QString warn = QString("Invalid entry in --refresh list: %1")
                                .arg(*i);

            bool enable = (*i).contains("not") ? false : true;

            if ((*i).contains("today"))
                fill_data.SetRefresh(0, enable);
            else if ((*i).contains("tomorrow"))
                fill_data.SetRefresh(1, enable);
            else if ((*i).contains("second"))
                fill_data.SetRefresh(2, enable);
            else if ((*i).contains("all"))
                fill_data.SetRefresh(FillData::kRefreshAll, enable);
            else if ((*i).contains("-"))
            {
                bool ok;
                QStringList r = (*i).split("-");

                uint lower = r[0].toUInt(&ok);
                if (!ok)
                {
                    cerr << warn.toLocal8Bit().constData() << endl;
                    return false;
                }

                uint upper = r[1].toUInt(&ok);
                if (!ok)
                {
                    cerr << warn.toLocal8Bit().constData() << endl;
                    return false;
                }

                if (lower > upper)
                {
                    cerr << warn.toLocal8Bit().constData() << endl;
                    return false;
                }

                for (uint j = lower; j <= upper; ++j)
                    fill_data.SetRefresh(j, true);
            }
            else
            {
                bool ok;
                uint day = (*i).toUInt(&ok);
                if (!ok)
                {
                    cerr << warn.toLocal8Bit().constData() << endl;
                    return false;
                }

                fill_data.SetRefresh(day, true);
            }
        }
    }

    if (cmdline.toBool("dontrefreshtba"))
        fill_data.refresh_tba = false;
    if (cmdline.toBool("ddgraball"))
    {
        fill_data.SetRefresh(FillData::kRefreshClear, false);
        fill_data.dd_grab_all = true;
    }
    if (cmdline.toBool("onlychannels"))
        fill_data.only_update_channels = true;

    mark_repeats = cmdline.toBool("markrepeats");

    CleanupGuard callCleanup(cleanup);

#ifndef _WIN32
    QList<int> signallist;
    signallist << SIGINT << SIGTERM << SIGSEGV << SIGABRT << SIGBUS << SIGFPE
               << SIGILL;
#if ! CONFIG_DARWIN
    signallist << SIGRTMIN;
#endif
    SignalHandler::Init(signallist);
    signal(SIGHUP, SIG_IGN);
#endif

    gContext = new MythContext(MYTH_BINARY_VERSION);
    if (!gContext->Init(false))
    {
        LOG(VB_GENERAL, LOG_ERR, "Failed to init MythContext, exiting.");
        return GENERIC_EXIT_NO_MYTHCONTEXT;
    }

    setHttpProxy();

    MythTranslation::load("mythfrontend");

    if (!UpgradeTVDatabaseSchema(false))
    {
        LOG(VB_GENERAL, LOG_ERR, "Incorrect database schema");
        return GENERIC_EXIT_DB_OUTOFDATE;
    }

    if (gCoreContext->SafeConnectToMasterServer(true, false))
        LOG(VB_GENERAL, LOG_INFO,
            "Opening blocking connection to master backend");
    else
        LOG(VB_GENERAL, LOG_WARNING,
            "Failed to connect to master backend. MythFillDatabase will "
            "continue running but will be unable to prevent backend from "
            "shutting down, or triggering a reschedule when complete.");

    if (from_file)
    {
        QString status = QObject::tr("currently running.");
        QDateTime GuideDataBefore, GuideDataAfter;

        updateLastRunStart();
        updateLastRunStatus(status);

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("SELECT MAX(endtime) FROM program p LEFT JOIN channel c "
                      "ON p.chanid=c.chanid WHERE c.sourceid= :SRCID "
                      "AND manualid = 0 AND c.xmltvid != '';");
        query.bindValue(":SRCID", fromfile_id);

        if (query.exec() && query.next())
        {
            if (!query.isNull(0))
                GuideDataBefore =
                    MythDate::fromString(query.value(0).toString());
        }

        if (!fill_data.GrabDataFromFile(fromfile_id, fromfile_name))
        {
            return GENERIC_EXIT_NOT_OK;
        }

        updateLastRunEnd();

        query.prepare("SELECT MAX(endtime) FROM program p LEFT JOIN channel c "
                      "ON p.chanid=c.chanid WHERE c.sourceid= :SRCID "
                      "AND manualid = 0 AND c.xmltvid != '';");
        query.bindValue(":SRCID", fromfile_id);

        if (query.exec() && query.next())
        {
            if (!query.isNull(0))
                GuideDataAfter =
                    MythDate::fromString(query.value(0).toString());
        }

        if (GuideDataAfter == GuideDataBefore)
            status = QObject::tr("mythfilldatabase ran, but did not insert "
                    "any new data into the Guide.  This can indicate a "
                    "potential problem with the XML file used for the update.");
        else
            status = QObject::tr("Successful.");

        updateLastRunStatus(status);
    }
    else if (from_dd_file)
    {
        fill_data.GrabDataFromDDFile(
            fromfile_id, fromfile_offset, fromfile_name, fromddfile_lineupid);
    }
    else
    {
        SourceList sourcelist;

        MSqlQuery sourcequery(MSqlQuery::InitCon());
        QString where;

        if (sourceid != -1)
        {
            LOG(VB_GENERAL, LOG_INFO,
                QString("Running for sourceid %1 ONLY because --sourceid "
                        "was given on command-line").arg(sourceid));
            where = QString("WHERE sourceid = %1").arg(sourceid);
        }

        QString querystr = QString("SELECT sourceid,name,xmltvgrabber,userid,"
                                   "password,lineupid "
                                   "FROM videosource ") + where +
                                   QString(" ORDER BY sourceid;");

        if (sourcequery.exec(querystr))
        {
             if (sourcequery.size() > 0)
             {
                  while (sourcequery.next())
                  {
                       Source newsource;

                       newsource.id = sourcequery.value(0).toInt();
                       newsource.name = sourcequery.value(1).toString();
                       newsource.xmltvgrabber = sourcequery.value(2).toString();
                       newsource.userid = sourcequery.value(3).toString();
                       newsource.password = sourcequery.value(4).toString();
                       newsource.lineupid = sourcequery.value(5).toString();

                       newsource.xmltvgrabber_baseline = false;
                       newsource.xmltvgrabber_manualconfig = false;
                       newsource.xmltvgrabber_cache = false;
                       newsource.xmltvgrabber_prefmethod = "";

                       sourcelist.push_back(newsource);
                       usingDataDirect |=
                           is_grabber_datadirect(newsource.xmltvgrabber);
                  }
             }
             else
             {
                  LOG(VB_GENERAL, LOG_ERR,
                      "There are no channel sources defined, did you run "
                      "the setup program?");
                  return GENERIC_EXIT_SETUP_ERROR;
             }
        }
        else
        {
             MythDB::DBError("loading channel sources", sourcequery);
             return GENERIC_EXIT_DB_ERROR;
        }

        if (!fill_data.Run(sourcelist))
            LOG(VB_GENERAL, LOG_ERR, "Failed to fetch some program info");
        else
            LOG(VB_GENERAL, LOG_NOTICE, "Data fetching complete.");
    }

    if (fill_data.only_update_channels && !fill_data.need_post_grab_proc)
    {
        return GENERIC_EXIT_OK;
    }

    LOG(VB_GENERAL, LOG_INFO, "Adjusting program database end times.");
    int update_count = ProgramData::fix_end_times();
    if (update_count == -1)
        LOG(VB_GENERAL, LOG_ERR, "fix_end_times failed!");
    else
        LOG(VB_GENERAL, LOG_INFO,
            QString("    %1 replacements made").arg(update_count));

    LOG(VB_GENERAL, LOG_INFO, "Marking generic episodes.");

    MSqlQuery query(MSqlQuery::InitCon());
    query.prepare("UPDATE program SET generic = 1 WHERE "
        "((programid = '' AND subtitle = '' AND description = '') OR "
        " (programid <> '' AND category_type = 'series' AND "
        "  program.programid LIKE '%0000'));");

    if (!query.exec())
        MythDB::DBError("mark generic", query);
    else
        LOG(VB_GENERAL, LOG_INFO,
            QString("    Found %1").arg(query.numRowsAffected()));

    LOG(VB_GENERAL, LOG_INFO, "Extending non-unique programids "
                                "with multiple parts.");

    int found = 0;
    MSqlQuery sel(MSqlQuery::InitCon());
    sel.prepare("SELECT DISTINCT programid, partnumber, parttotal "
                "FROM program WHERE partnumber > 0 AND parttotal > 0 AND "
                "programid LIKE '%0000'");
    if (sel.exec())
    {
        MSqlQuery repl(MSqlQuery::InitCon());
        repl.prepare("UPDATE program SET programid = :NEWID "
                        "WHERE programid = :OLDID AND "
                        "partnumber = :PARTNUM AND "
                        "parttotal = :PARTTOTAL");

        while (sel.next())
        {
            QString orig_programid = sel.value(0).toString();
            QString new_programid = orig_programid.left(10);
            int     partnum, parttotal;
            QString part;

            partnum   = sel.value(1).toInt();
            parttotal = sel.value(2).toInt();

            part.setNum(parttotal);
            new_programid.append(part.rightJustified(2, '0'));
            part.setNum(partnum);
            new_programid.append(part.rightJustified(2, '0'));

            LOG(VB_GENERAL, LOG_INFO,
                QString("    %1 -> %2 (part %3 of %4)")
                    .arg(orig_programid).arg(new_programid)
                    .arg(partnum).arg(parttotal));

            repl.bindValue(":NEWID", new_programid);
            repl.bindValue(":OLDID", orig_programid);
            repl.bindValue(":PARTNUM",   partnum);
            repl.bindValue(":PARTTOTAL", parttotal);
            if (!repl.exec())
            {
                LOG(VB_GENERAL, LOG_INFO,
                    QString("Fudging programid from '%1' to '%2'")
                        .arg(orig_programid)
                        .arg(new_programid));
            }
            else
                found += repl.numRowsAffected();
        }
    }

    LOG(VB_GENERAL, LOG_INFO, QString("    Found %1").arg(found));

    LOG(VB_GENERAL, LOG_INFO, "Fixing missing original airdates.");
    query.prepare("UPDATE program p "
                    "JOIN ( "
                    "  SELECT programid, MAX(originalairdate) maxoad "
                    "  FROM program "
                    "  WHERE programid <> '' AND "
                    "        originalairdate IS NOT NULL "
                    "  GROUP BY programid ) oad "
                    "  ON p.programid = oad.programid "
                    "SET p.originalairdate = oad.maxoad "
                    "WHERE p.originalairdate IS NULL");

    if (query.exec())
        LOG(VB_GENERAL, LOG_INFO,
            QString("    Found %1 with programids")
            .arg(query.numRowsAffected()));

    query.prepare("UPDATE program p "
                    "JOIN ( "
                    "  SELECT title, subtitle, description, "
                    "         MAX(originalairdate) maxoad "
                    "  FROM program "
                    "  WHERE programid = '' AND "
                    "        originalairdate IS NOT NULL "
                    "  GROUP BY title, subtitle, description ) oad "
                    "  ON p.programid = '' AND "
                    "     p.title = oad.title AND "
                    "     p.subtitle = oad.subtitle AND "
                    "     p.description = oad.description "
                    "SET p.originalairdate = oad.maxoad "
                    "WHERE p.originalairdate IS NULL");

    if (query.exec())
        LOG(VB_GENERAL, LOG_INFO,
            QString("    Found %1 without programids")
            .arg(query.numRowsAffected()));

    if (mark_repeats)
    {
        LOG(VB_GENERAL, LOG_INFO, "Marking repeats.");

        int newEpiWindow = gCoreContext->GetNumSetting( "NewEpisodeWindow", 14);

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("UPDATE program SET previouslyshown = 1 "
                      "WHERE previouslyshown = 0 "
                      "AND originalairdate is not null "
                      "AND (to_days(starttime) - to_days(originalairdate)) "
                      "    > :NEWWINDOW;");
        query.bindValue(":NEWWINDOW", newEpiWindow);

        if (query.exec())
            LOG(VB_GENERAL, LOG_INFO,
                QString("    Found %1").arg(query.numRowsAffected()));

        LOG(VB_GENERAL, LOG_INFO, "Unmarking new episode rebroadcast repeats.");
        query.prepare("UPDATE program SET previouslyshown = 0 "
                      "WHERE previouslyshown = 1 "
                      "AND originalairdate is not null "
                      "AND (to_days(starttime) - to_days(originalairdate)) "
                      "    <= :NEWWINDOW;");
        query.bindValue(":NEWWINDOW", newEpiWindow);

        if (query.exec())
            LOG(VB_GENERAL, LOG_INFO,
                QString("    Found %1").arg(query.numRowsAffected()));
    }

    // Mark first and last showings
    MSqlQuery updt(MSqlQuery::InitCon());
    updt.prepare("UPDATE program SET first = 0, last = 0;");
    if (!updt.exec())
        MythDB::DBError("Clearing first and last showings", updt);

    LOG(VB_GENERAL, LOG_INFO, "Marking episode first showings.");
    updt.prepare("UPDATE program "
                    "JOIN (SELECT MIN(p.starttime) AS starttime, p.programid "
                    "      FROM program p, channel c "
                    "      WHERE p.programid <> '' "
                    "            AND p.chanid = c.chanid "
                    "            AND c.visible = 1 "
                    "      GROUP BY p.programid "
                    "     ) AS firsts "
                    "ON program.programid = firsts.programid "
                    "  AND program.starttime = firsts.starttime "
                    "SET program.first=1;");
    if (!updt.exec())
        MythDB::DBError("Marking first showings by id", updt);
    found = updt.numRowsAffected();

    updt.prepare("UPDATE program "
                    "JOIN (SELECT MIN(p.starttime) AS starttime, p.title, p.subtitle, "
                    "           LEFT(p.description, 1024) AS partdesc "
                    "      FROM program p, channel c "
                    "      WHERE p.programid = '' "
                    "            AND p.chanid = c.chanid "
                    "            AND c.visible = 1 "
                    "      GROUP BY p.title, p.subtitle, partdesc "
                    "     ) AS firsts "
                    "ON program.starttime = firsts.starttime "
                    "  AND program.title = firsts.title "
                    "  AND program.subtitle = firsts.subtitle "
                    "  AND LEFT(program.description, 1024) = firsts.partdesc "
                    "SET program.first = 1 "
                    "WHERE program.programid = '';");
    if (!updt.exec())
        MythDB::DBError("Marking first showings", updt);
    found += updt.numRowsAffected();
    LOG(VB_GENERAL, LOG_INFO, QString("    Found %1").arg(found));

    LOG(VB_GENERAL, LOG_INFO, "Marking episode last showings.");
    updt.prepare("UPDATE program "
                    "JOIN (SELECT MAX(p.starttime) AS starttime, p.programid "
                    "      FROM program p, channel c "
                    "      WHERE p.programid <> '' "
                    "            AND p.chanid = c.chanid "
                    "            AND c.visible = 1 "
                    "      GROUP BY p.programid "
                    "     ) AS lasts "
                    "ON program.programid = lasts.programid "
                    "  AND program.starttime = lasts.starttime "
                    "SET program.last=1;");
    if (!updt.exec())
        MythDB::DBError("Marking last showings by id", updt);
    found = updt.numRowsAffected();

    updt.prepare("UPDATE program "
                    "JOIN (SELECT MAX(p.starttime) AS starttime, p.title, p.subtitle, "
                    "           LEFT(p.description, 1024) AS partdesc "
                    "      FROM program p, channel c "
                    "      WHERE p.programid = '' "
                    "            AND p.chanid = c.chanid "
                    "            AND c.visible = 1 "
                    "      GROUP BY p.title, p.subtitle, partdesc "
                    "     ) AS lasts "
                    "ON program.starttime = lasts.starttime "
                    "  AND program.title = lasts.title "
                    "  AND program.subtitle = lasts.subtitle "
                    "  AND LEFT(program.description, 1024) = lasts.partdesc "
                    "SET program.last = 1 "
                    "WHERE program.programid = '';");
    if (!updt.exec())
        MythDB::DBError("Marking last showings", updt);
    found += updt.numRowsAffected();
    LOG(VB_GENERAL, LOG_INFO, QString("    Found %1").arg(found));

    if (1) // limit MSqlQuery's lifetime
    {
        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("SELECT count(previouslyshown) "
                      "FROM program WHERE previouslyshown = 1;");
        if (query.exec() && query.next())
        {
            if (query.value(0).toInt() != 0)
                gCoreContext->SaveSettingOnHost("HaveRepeats", "1", NULL);
            else
                gCoreContext->SaveSettingOnHost("HaveRepeats", "0", NULL);
        }
    }

    if ((usingDataDirect) &&
        (gCoreContext->GetNumSetting("MythFillGrabberSuggestsTime", 1)))
    {
        fill_data.ddprocessor.GrabNextSuggestedTime();
    }

    LOG(VB_GENERAL, LOG_INFO, "\n"
            "===============================================================\n"
            "| Attempting to contact the master backend for rescheduling.  |\n"
            "| If the master is not running, rescheduling will happen when |\n"
            "| the master backend is restarted.                            |\n"
            "===============================================================");

    if (mark_repeats)
        ScheduledRecording::RescheduleMatch(0, 0, 0, QDateTime(),
                                            "MythFillDatabase");

    gCoreContext->SendMessage("CLEAR_SETTINGS_CACHE");

    gCoreContext->SendSystemEvent("MYTHFILLDATABASE_RAN");

    LOG(VB_GENERAL, LOG_NOTICE, "mythfilldatabase run complete.");

    return GENERIC_EXIT_OK;
}
Exemple #5
0
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    FillData fill_data;
    int argpos = 1;
    int fromfile_id = 1;
    int fromfile_offset = 0;
    QString fromfile_name;
    bool from_xawfile = false;
    int fromxawfile_id = 1;
    QString fromxawfile_name;
    bool from_file = false;
    bool mark_repeats = true;

    bool usingDataDirect = false;
    bool grab_data = true;

    bool export_iconmap = false;
    bool import_iconmap = false;
    bool reset_iconmap = false;
    bool reset_iconmap_icons = false;
    QString import_icon_data_filename("iconmap.xml");
    QString export_icon_data_filename("iconmap.xml");

    bool update_icon_data = false;

    bool from_dd_file = false;
    int sourceid = -1;
    QString fromddfile_lineupid;

    QFileInfo finfo(a.argv()[0]);
    QString binname = finfo.baseName();

    myth_nice(19);

    while (argpos < a.argc())
    {
        // The manual and update flags should be mutually exclusive.
        if (!strcmp(a.argv()[argpos], "--manual"))
        {
            cout << "###\n";
            cout << "### Running in manual channel configuration mode.\n";
            cout << "### This will ask you questions about every channel.\n";
            cout << "###\n";
            fill_data.chan_data.interactive = true;
        }
        else if (!strcmp(a.argv()[argpos], "--preset"))
        {
            // For using channel preset values instead of channel numbers.
            cout << "###\n";
            cout << "### Running in preset channel configuration mode.\n";
            cout << "### This will assign channel ";
            cout << "preset numbers to every channel.\n";
            cout << "###\n";
            fill_data.chan_data.channel_preset = true;
        }
        else if (!strcmp(a.argv()[argpos], "--update"))
        {
            // For running non-destructive updates on the database for
            // users in xmltv zones that do not provide channel data.
            fill_data.chan_data.non_us_updating = true;
        }
        else if (!strcmp(a.argv()[argpos], "--file"))
        {
            if (((argpos + 2) >= a.argc()) ||
                !strncmp(a.argv()[argpos + 1], "--", 2) ||
                !strncmp(a.argv()[argpos + 2], "--", 2))
            {
                printf("missing or invalid parameters for --file option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            if (!fromfile_name.isEmpty())
            {
                printf("only one --file option allowed\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            fromfile_id = atoi(a.argv()[++argpos]);
            fromfile_name = a.argv()[++argpos];

            VERBOSE(VB_GENERAL, "Bypassing grabbers, reading directly from file");
            from_file = true;
        }
        else if (!strcmp(a.argv()[argpos], "--dd-file"))
        {
            if (((argpos + 4) >= a.argc()) ||
                !strncmp(a.argv()[argpos + 1], "--", 2) ||
                !strncmp(a.argv()[argpos + 2], "--", 2) ||
                !strncmp(a.argv()[argpos + 3], "--", 2) ||
                !strncmp(a.argv()[argpos + 4], "--", 2))
            {
                printf("missing or invalid parameters for --dd-file option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            if (!fromfile_name.isEmpty())
            {
                printf("only one --dd-file option allowed\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            fromfile_id = atoi(a.argv()[++argpos]);
            fromfile_offset = atoi(a.argv()[++argpos]);
            fromddfile_lineupid = a.argv()[++argpos];
            fromfile_name = a.argv()[++argpos];

            VERBOSE(VB_GENERAL, "Bypassing grabbers, reading directly from file");
            from_dd_file = true;
        }
        else if (!strcmp(a.argv()[argpos], "--xawchannels"))
        {
            if (((argpos + 2) >= a.argc()) ||
                !strncmp(a.argv()[argpos + 1], "--", 2) ||
                !strncmp(a.argv()[argpos + 2], "--", 2))
            {
                printf("missing or invalid parameters for --xawchannels option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            if (!fromxawfile_name.isEmpty())
            {
                printf("only one --xawchannels option allowed\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            fromxawfile_id = atoi(a.argv()[++argpos]);
            fromxawfile_name = a.argv()[++argpos];

            VERBOSE(VB_GENERAL, "Reading channels from xawtv configfile");
            from_xawfile = true;
        }
        else if (!strcmp(a.argv()[argpos], "--do-channel-updates"))
        {
            fill_data.chan_data.channel_updates = true;
        }
        else if (!strcmp(a.argv()[argpos], "--remove-new-channels"))
        {
            fill_data.chan_data.remove_new_channels = true;
        }
        else if (!strcmp(a.argv()[argpos], "--do-not-filter-new-channels"))
        {
            fill_data.chan_data.filter_new_channels = false;
        }
        else if (!strcmp(a.argv()[argpos], "--graboptions"))
        {
            if (((argpos + 1) >= a.argc()))
            {
                printf("missing parameter for --graboptions option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            fill_data.graboptions = QString(" ") + QString(a.argv()[++argpos]);
        }
        else if (!strcmp(a.argv()[argpos], "--sourceid"))
        {
            if (((argpos + 1) >= a.argc()))
            {
                printf("missing parameter for --sourceid option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            sourceid = QString(a.argv()[++argpos]).toInt();
        }
        else if (!strcmp(a.argv()[argpos], "--cardtype"))
        {
            if (!sourceid)
            {
                printf("--cardtype option must follow a --sourceid option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            if (((argpos + 1) >= a.argc()))
            {
                printf("missing parameter for --cardtype option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            fill_data.chan_data.cardtype =
                QString(a.argv()[++argpos]).trimmed().toUpper();
        }
        else if (!strcmp(a.argv()[argpos], "--max-days"))
        {
            if (((argpos + 1) >= a.argc()))
            {
                printf("missing parameter for --max-days option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            fill_data.maxDays = QString(a.argv()[++argpos]).toUInt();

            if (fill_data.maxDays < 1)
            {
                printf("ignoring invalid parameter for --max-days\n");
                fill_data.maxDays = 0;
            }
            else if (fill_data.maxDays == 1)
            {
                fill_data.SetRefresh(0, true);
            }
        }
        else if (!strcmp(a.argv()[argpos], "--refresh-today"))
        {
            fill_data.SetRefresh(0, true);
        }
        else if (!strcmp(a.argv()[argpos], "--dont-refresh-tomorrow"))
        {
            fill_data.SetRefresh(1, false);
        }
        else if (!strcmp(a.argv()[argpos], "--refresh-second"))
        {
            fill_data.SetRefresh(2, true);
        }
        else if (!strcmp(a.argv()[argpos], "--refresh-all"))
        {
            fill_data.SetRefresh(FillData::kRefreshAll, true);
        }
        else if (!strcmp(a.argv()[argpos], "--refresh-day"))
        {
            if (((argpos + 1) >= a.argc()))
            {
                printf("missing parameter for --refresh-day option\n");
                return FILLDB_EXIT_INVALID_CMDLINE;
            }

            bool ok = true;
            uint day = QString(a.argv()[++argpos]).toUInt(&ok);

            if (!ok)
            {
                printf("ignoring invalid parameter for --refresh-day\n");
            }
            else
            {
                fill_data.SetRefresh(day, true);
            }
        }
        else if (!strcmp(a.argv()[argpos], "--dont-refresh-tba"))
        {
            fill_data.refresh_tba = false;
        }
        else if (!strcmp(a.argv()[argpos], "--only-update-channels"))
        {
            fill_data.only_update_channels = true;
        }
        else if (!strcmp(a.argv()[argpos],"-v") ||
                 !strcmp(a.argv()[argpos],"--verbose"))
        {
            if (a.argc()-1 > argpos)
            {
                if (parse_verbose_arg(a.argv()[argpos+1]) ==
                        GENERIC_EXIT_INVALID_CMDLINE)
                    return GENERIC_EXIT_INVALID_CMDLINE;

                ++argpos;
            }
            else
            {
                cerr << "Missing argument to -v/--verbose option\n";
                return GENERIC_EXIT_INVALID_CMDLINE;
            }
        }
        else if (!strcmp(a.argv()[argpos], "--dd-grab-all"))
        {
            fill_data.SetRefresh(FillData::kRefreshClear, false);
            fill_data.dd_grab_all = true;
        }
        else if (!strcmp(a.argv()[argpos], "--quiet"))
        {
            print_verbose_messages = VB_NONE;
        }
        else if (!strcmp(a.argv()[argpos], "--mark-repeats"))
        {
             mark_repeats = true;
        }
        else if (!strcmp(a.argv()[argpos], "--nomark-repeats"))
        {
            mark_repeats = false;
        }
        else if (!strcmp(a.argv()[argpos], "--export-icon-map"))
        {
            export_iconmap = true;
            grab_data = false;

            if ((argpos + 1) >= a.argc() ||
                    !strncmp(a.argv()[argpos + 1], "--", 2))
            {
                if (!isatty(fileno(stdout)))
                {
                    export_icon_data_filename = '-';
                }
            }
            else
            {
                export_icon_data_filename = a.argv()[++argpos];
            }
        }
        else if (!strcmp(a.argv()[argpos], "--import-icon-map"))
        {
            import_iconmap = true;
            grab_data = false;

            if ((argpos + 1) >= a.argc() ||
                    !strncmp(a.argv()[argpos + 1], "--", 2))
            {
                if (!isatty(fileno(stdin)))
                {
                    import_icon_data_filename = '-';
                }
            }
            else
            {
                import_icon_data_filename = a.argv()[++argpos];
            }
        }
        else if (!strcmp(a.argv()[argpos], "--update-icon-map"))
        {
            update_icon_data = true;
            grab_data = false;
        }
        else if (!strcmp(a.argv()[argpos], "--reset-icon-map"))
        {
            reset_iconmap = true;
            grab_data = false;

            if ((argpos + 1) < a.argc() &&
                    strncmp(a.argv()[argpos + 1], "--", 2))
            {
                ++argpos;
                if (QString(a.argv()[argpos]) == "all")
                {
                    reset_iconmap_icons = true;
                }
                else
                {
                    cerr << "Unknown icon group '" << a.argv()[argpos]
                            << "' for --reset-icon-map option" << endl;
                    return FILLDB_EXIT_UNKNOWN_ICON_GROUP;
                }
            }
        }
        else if (!strcmp(a.argv()[argpos], "-h") ||
                 !strcmp(a.argv()[argpos], "--help"))
        {
            cout << "usage:\n";
            cout << "--manual\n";
            cout << "   Run in manual channel configuration mode\n";
            cout << "   This will ask you questions about every channel\n";
            cout << "\n";
            cout << "--update\n";
            cout << "   For running non-destructive updates on the database for\n";
            cout << "   users in xmltv zones that do not provide channel data\n";
            cout << "   Stops the addition of new channels and the changing of channel icons.\n";
            cout << "\n";
            cout << "--preset\n";
            cout << "   Use it in case that you want to assign a preset number for\n";
            cout << "   each channel, useful for non US countries where people\n";
            cout << "   are used to assigning a sequenced number for each channel, i.e.:\n";
            cout << "   1->TVE1(S41), 2->La 2(SE18), 3->TV3(21), 4->Canal 33(60)...\n";
            cout << "\n";
            cout << "--file <sourceid> <xmlfile>\n";
            cout << "   Bypass the grabbers and read data directly from a file\n";
            cout << "   <sourceid> = number of the video source to use with this file\n";
            cout << "   <xmlfile>  = file to read\n";
            cout << "\n";
            cout << "--dd-file <sourceid> <offset> <lineupid> <xmlfile>\n";
            cout << "   <sourceid> = number of the video source to use with this file\n";
            cout << "   <offset>   = days from today that xmlfile defines\n";
            cout << "                (-1 means to replace all data, up to 10 days)\n";
            cout << "   <lineupid> = the lineup id\n";
            cout << "   <xmlfile>  = file to read\n";
            cout << "\n";
            cout << "--xawchannels <sourceid> <xawtvrcfile>\n";
            cout << "   (--manual flag works in combination with this)\n";
            cout << "   Read channels as defined in xawtvrc file given\n";
            cout << "   <sourceid>    = cardinput\n";
            cout << "   <xawtvrcfile> = file to read\n";
            cout << "\n";
            cout << "--do-channel-updates\n";
            cout << "   When using DataDirect, ask mythfilldatabase to\n";
            cout << "   overwrite channel names, frequencies, etc. with the\n";
            cout << "   values available from the data source. This will \n";
            cout << "   override custom channel names, which is why it is\n";
            cout << "   off by default.\n";
            cout << "\n";
            cout << "--remove-new-channels\n";
            cout << "   When using DataDirect, ask mythfilldatabase to\n";
            cout << "   remove new channels (those not in the database)\n";
            cout << "   from the DataDirect lineup.  These channels are\n";
            cout << "   removed from the lineup as if you had done so\n";
            cout << "   via the DataDirect website's Lineup Wizard, but\n";
            cout << "   may be re-added manually and incorporated into\n";
            cout << "   MythTV by running mythfilldatabase without this\n";
            cout << "   option.  New channels are automatically removed\n";
            cout << "   for DVB and HDTV sources that use DataDirect.\n";
            cout << "\n";
            cout << "--do-not-filter-new-channels\n";
            cout << "   Normally MythTV tries to avoid adding ATSC channels\n";
            cout << "   to NTSC channel lineups. This option restores the\n";
            cout << "   behaviour of adding every channel in the downloaded\n";
            cout << "   channel lineup to MythTV's lineup, in case MythTV's\n";
            cout << "   smarts fail you.\n";
            cout << "\n";
            cout << "--graboptions <\"options\">\n";
            cout << "   Pass options to grabber. Do NOT use unless you know\n";
            cout << "   what you are doing. Mythfilldatabase will\n";
            cout << "   automatically use the correct options for xmltv\n";
            cout << "   compliant grabbers.\n";
            cout << "\n";
            cout << "--sourceid <number>\n";
            cout << "   Only refresh data for sourceid given\n";
            cout << "\n";
            cout << "--max-days <number>\n";
            cout << "   Force the maximum number of days, counting today,\n";
            cout << "   for the grabber to check for future listings\n";
            cout << "--only-update-channels\n";
            cout << "   Get as little listings data as possible to update channels\n";
            cout << "--refresh-today\n";
            cout << "--refresh-second\n";
            cout << "--refresh-all\n";
            cout << "--refresh-day <number>";
            cout << "   (Only valid for selected grabbers: e.g. DataDirect)\n";
            cout << "   Force a refresh today, two days, every day, or a specific day from now,\n";
            cout << "   to catch the latest changes.  --refresh-all will update every day except\n";
            cout << "   the current day. To refresh today and all following days, XMLTV users\n";
            cout << "   should combine --refresh-today and --refresh-all.  Schedules Direct/\n";
            cout << "   DataDirect users should use --dd-grab-all.\n";
            cout << "--dont-refresh-tomorrow\n";
            cout << "   Tomorrow will always be refreshed unless this argument is used\n";
            cout << "--dont-refresh-tba\n";
            cout << "   \"To be announced\" programs will always be refreshed \n";
            cout << "   unless this argument is used\n";
            cout << "\n";
            cout << "--dd-grab-all\n";
            cout << "   The DataDirect grabber will grab all available data\n";
            cout << "   in a single pull. This will ensure you always have\n";
            cout << "   the most up-to-date data, but requires significantly\n";
            cout << "   more CPU and RAM. It is not expected to work on all\n";
            cout << "   backend systems and with all lineups, and may\n";
            cout << "   interfere with recording due to resource starvation.\n";
            cout << "\n";
            cout << "--export-icon-map [<filename>]\n";
            cout << "   Exports your current icon map to <filename> (default: "
                 << export_icon_data_filename.toLocal8Bit().constData()
                 << ")\n";
            cout << "--import-icon-map [<filename>]\n";
            cout << "   Imports an icon map from <filename> (default: "
                 << import_icon_data_filename.toLocal8Bit().constData()
                 << ")\n";
            cout << "--update-icon-map\n";
            cout << "   Updates icon map icons only\n";
            cout << "--reset-icon-map [all]\n";
            cout << "   Resets your icon map (pass all to reset channel icons as well)\n";
            cout << "\n";
            cout << "--mark-repeats\n";
            cout << "   Marks any programs with a OriginalAirDate earlier\n";
            cout << "   than their start date as a repeat\n";
            cout << "\n";
            cout << "-v or --verbose debug-level\n";
            cout << "   Use '-v help' for level info\n";
            cout << "\n";

            cout << "--help\n";
            cout << "   This text\n";
            cout << "\n";
            cout << "\n";
            cout << "  --manual and --update cannot be used together.\n";
            cout << "\n";
            return FILLDB_EXIT_INVALID_CMDLINE;
        }
        else if (!strcmp(a.argv()[argpos], "--no-delete"))
        {
            cerr << "Deprecated option '" << a.argv()[argpos] << "'" << endl;
        }
#ifdef Q_WS_MACX
        else if (!strncmp(argv[argpos],"-psn_",5))
        {
            cerr << "Ignoring Process Serial Number from command line\n";
        }
#endif
        else
        {
            fprintf(stderr, "illegal option: '%s' (use --help)\n",
                    a.argv()[argpos]);
            return FILLDB_EXIT_INVALID_CMDLINE;
        }

        ++argpos;
    }

    CleanupGuard callCleanup(cleanup);

    gContext = new MythContext(MYTH_BINARY_VERSION);
    if (!gContext->Init(false))
    {
        VERBOSE(VB_IMPORTANT, "Failed to init MythContext, exiting.");
        return FILLDB_EXIT_NO_MYTHCONTEXT;
    }

    gCoreContext->SetAppName(binname);

    MythTranslation::load("mythfrontend");

    if (!UpgradeTVDatabaseSchema(false))
    {
        VERBOSE(VB_IMPORTANT, "Incorrect database schema");
        return GENERIC_EXIT_DB_OUTOFDATE;
    }

    gCoreContext->LogEntry("mythfilldatabase", LP_INFO,
                       "Listings Download Started", "");

    if (!grab_data)
    {
    }
    else if (from_xawfile)
    {
        fill_data.readXawtvChannels(fromxawfile_id, fromxawfile_name);
    }
    else if (from_file)
    {
        QString status = QObject::tr("currently running.");
        QDateTime GuideDataBefore, GuideDataAfter;

        MSqlQuery query(MSqlQuery::InitCon());
        updateLastRunStart(query);
        updateLastRunStatus(query, status);

        query.prepare("SELECT MAX(endtime) FROM program p LEFT JOIN channel c "
                      "ON p.chanid=c.chanid WHERE c.sourceid= :SRCID "
                      "AND manualid = 0;");
        query.bindValue(":SRCID", fromfile_id);

        if (query.exec() && query.next())
        {
            if (!query.isNull(0))
                GuideDataBefore = QDateTime::fromString(query.value(0).toString(),
                                                    Qt::ISODate);
        }

        if (!fill_data.GrabDataFromFile(fromfile_id, fromfile_name))
        {
            return FILLDB_EXIT_GRAB_DATA_FAILED;
        }

        updateLastRunEnd(query);

        query.prepare("SELECT MAX(endtime) FROM program p LEFT JOIN channel c "
                      "ON p.chanid=c.chanid WHERE c.sourceid= :SRCID "
                      "AND manualid = 0;");
        query.bindValue(":SRCID", fromfile_id);

        if (query.exec() && query.next())
        {
            if (!query.isNull(0))
                GuideDataAfter = QDateTime::fromString(query.value(0).toString(),
                                                   Qt::ISODate);
        }

        if (GuideDataAfter == GuideDataBefore)
            status = QObject::tr("mythfilldatabase ran, but did not insert "
                     "any new data into the Guide.  This can indicate a "
                     "potential problem with the XML file used for the update.");
        else
            status = QObject::tr("Successful.");

        updateLastRunStatus(query, status);
    }
    else if (from_dd_file)
    {
        fill_data.GrabDataFromDDFile(
            fromfile_id, fromfile_offset, fromfile_name, fromddfile_lineupid);
    }
    else
    {
        SourceList sourcelist;

        MSqlQuery sourcequery(MSqlQuery::InitCon());
        QString where = "";

        if (sourceid != -1)
        {
            VERBOSE(VB_GENERAL,
                    QString("Running for sourceid %1 ONLY because --sourceid "
                            "was given on command-line").arg(sourceid));
            where = QString("WHERE sourceid = %1").arg(sourceid);
        }

        QString querystr = QString("SELECT sourceid,name,xmltvgrabber,userid,"
                                   "password,lineupid "
                                   "FROM videosource ") + where +
                                   QString(" ORDER BY sourceid;");

        if (sourcequery.exec(querystr))
        {
             if (sourcequery.size() > 0)
             {
                  while (sourcequery.next())
                  {
                       Source newsource;

                       newsource.id = sourcequery.value(0).toInt();
                       newsource.name = sourcequery.value(1).toString();
                       newsource.xmltvgrabber = sourcequery.value(2).toString();
                       newsource.userid = sourcequery.value(3).toString();
                       newsource.password = sourcequery.value(4).toString();
                       newsource.lineupid = sourcequery.value(5).toString();

                       newsource.xmltvgrabber_baseline = false;
                       newsource.xmltvgrabber_manualconfig = false;
                       newsource.xmltvgrabber_cache = false;
                       newsource.xmltvgrabber_prefmethod = "";

                       sourcelist.push_back(newsource);
                       usingDataDirect |=
                           is_grabber_datadirect(newsource.xmltvgrabber);
                  }
             }
             else
             {
                  VERBOSE(VB_IMPORTANT,
                          "There are no channel sources defined, did you run "
                          "the setup program?");
                  gCoreContext->LogEntry("mythfilldatabase", LP_CRITICAL,
                                     "No channel sources defined",
                                     "Could not find any defined channel "
                                     "sources - did you run the setup "
                                     "program?");
                  return FILLDB_EXIT_NO_CHAN_SRC;
             }
        }
        else
        {
             MythDB::DBError("loading channel sources", sourcequery);
             return FILLDB_EXIT_DB_ERROR;
        }

        if (!fill_data.Run(sourcelist))
        {
             VERBOSE(VB_IMPORTANT, "Failed to fetch some program info");
             gCoreContext->LogEntry("mythfilldatabase", LP_WARNING,
                                "Failed to fetch some program info", "");
        }
        else
            VERBOSE(VB_IMPORTANT, "Data fetching complete.");
    }

    if (fill_data.only_update_channels && !fill_data.need_post_grab_proc)
    {
        return FILLDB_EXIT_OK;
    }

    if (reset_iconmap)
    {
        fill_data.icon_data.ResetIconMap(reset_iconmap_icons);
    }

    if (import_iconmap)
    {
        fill_data.icon_data.ImportIconMap(import_icon_data_filename);
    }

    if (export_iconmap)
    {
        fill_data.icon_data.ExportIconMap(export_icon_data_filename);
    }

    if (update_icon_data)
    {
        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("SELECT sourceid FROM videosource ORDER BY sourceid");
        if (!query.exec())
        {
            MythDB::DBError("Querying sources", query);
            return FILLDB_EXIT_DB_ERROR;
        }

        while (query.next())
            fill_data.icon_data.UpdateSourceIcons(query.value(0).toInt());
    }

    if (grab_data)
    {
        VERBOSE(VB_GENERAL, "Adjusting program database end times.");
        int update_count = ProgramData::fix_end_times();
        if (update_count == -1)
            VERBOSE(VB_IMPORTANT, "fix_end_times failed!");
        else
            VERBOSE(VB_GENERAL,
                    QString("    %1 replacements made").arg(update_count));

        gCoreContext->LogEntry("mythfilldatabase", LP_INFO,
                           "Listings Download Finished", "");
    }

    if (grab_data)
    {
        VERBOSE(VB_GENERAL, "Marking generic episodes.");

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("UPDATE program SET generic = 1 WHERE "
            "((programid = '' AND subtitle = '' AND description = '') OR "
            " (programid <> '' AND category_type = 'series' AND "
            "  program.programid LIKE '%0000'));");

        if (!query.exec())
            MythDB::DBError("mark generic", query);
        else
            VERBOSE(VB_GENERAL,
                    QString("    Found %1").arg(query.numRowsAffected()));
    }

    if (grab_data)
    {
        VERBOSE(VB_GENERAL, "Fudging non-unique programids "
                "with multiple parts.");

        int found = 0;
        MSqlQuery sel(MSqlQuery::InitCon());
        sel.prepare("SELECT DISTINCT programid, partnumber, parttotal "
                    "FROM program WHERE partnumber > 0 AND parttotal > 0 AND "
                    "programid LIKE '%0000'");
        if (sel.exec())
        {
            MSqlQuery repl(MSqlQuery::InitCon());

            while (sel.next())
            {
                QString orig_programid = sel.value(0).toString();
                QString new_programid = orig_programid.left(10);
                int     partnum, parttotal;
                QString part;

                partnum   = sel.value(1).toInt();
                parttotal = sel.value(2).toInt();

                part.setNum(parttotal);
                new_programid.append(part.rightJustified(2, '0'));
                part.setNum(partnum);
                new_programid.append(part.rightJustified(2, '0'));

                VERBOSE(VB_GENERAL,
                        QString("    %1 -> %2 (part %3 of %4)")
                        .arg(orig_programid).arg(new_programid)
                        .arg(partnum).arg(parttotal));

                repl.prepare("UPDATE program SET programid = :NEWID "
                             "WHERE programid = :OLDID AND "
                             "partnumber = :PARTNUM AND "
                             "parttotal = :PARTTOTAL");
                repl.bindValue(":NEWID", new_programid);
                repl.bindValue(":OLDID", orig_programid);
                repl.bindValue(":PARTNUM",   partnum);
                repl.bindValue(":PARTTOTAL", parttotal);
                if (!repl.exec())
                {
                    VERBOSE(VB_GENERAL,
                            QString("Fudging programid from '%1' to '%2'")
                            .arg(orig_programid)
                            .arg(new_programid));
                }
                else
                    found += repl.numRowsAffected();
            }
        }

        VERBOSE(VB_GENERAL, QString("    Found %1").arg(found));
    }

    if (mark_repeats)
    {
        VERBOSE(VB_GENERAL, "Marking repeats.");

        int newEpiWindow = gCoreContext->GetNumSetting( "NewEpisodeWindow", 14);

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("UPDATE program SET previouslyshown = 1 "
                      "WHERE previouslyshown = 0 "
                      "AND originalairdate is not null "
                      "AND (to_days(starttime) - to_days(originalairdate)) "
                      "    > :NEWWINDOW;");
        query.bindValue(":NEWWINDOW", newEpiWindow);

        if (query.exec())
            VERBOSE(VB_GENERAL,
                    QString("    Found %1").arg(query.numRowsAffected()));

        VERBOSE(VB_GENERAL, "Unmarking new episode rebroadcast repeats.");
        query.prepare("UPDATE program SET previouslyshown = 0 "
                      "WHERE previouslyshown = 1 "
                      "AND originalairdate is not null "
                      "AND (to_days(starttime) - to_days(originalairdate)) "
                      "    <= :NEWWINDOW;");
        query.bindValue(":NEWWINDOW", newEpiWindow);

        if (query.exec())
            VERBOSE(VB_GENERAL,
                    QString("    Found %1").arg(query.numRowsAffected()));
    }

    // Mark first and last showings

    if (grab_data)
    {
        MSqlQuery updt(MSqlQuery::InitCon());
        updt.prepare("UPDATE program SET first = 0, last = 0;");
        if (!updt.exec())
            MythDB::DBError("Clearing first and last showings", updt);

        VERBOSE(VB_GENERAL, "Marking episode first showings.");

        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("SELECT MIN(starttime),programid FROM program "
                      "WHERE programid > '' GROUP BY programid;");
        if (query.exec())
        {
            while(query.next())
            {
                updt.prepare("UPDATE program set first = 1 "
                             "WHERE starttime = :STARTTIME "
                             "  AND programid = :PROGRAMID;");
                updt.bindValue(":STARTTIME", query.value(0).toDateTime());
                updt.bindValue(":PROGRAMID", query.value(1).toString());
                if (!updt.exec())
                    MythDB::DBError("Marking first showings by id", updt);
            }
        }
        int found = query.size();
        query.prepare("SELECT MIN(starttime),title,subtitle,description "
                      "FROM program WHERE programid = '' "
                      "GROUP BY title,subtitle,description;");
        if (query.exec())
        {
            while(query.next())
            {
                updt.prepare("UPDATE program set first = 1 "
                             "WHERE starttime = :STARTTIME "
                             "  AND title = :TITLE "
                             "  AND subtitle = :SUBTITLE "
                             "  AND description = :DESCRIPTION");
                updt.bindValue(":STARTTIME", query.value(0).toDateTime());
                updt.bindValue(":TITLE", query.value(1).toString());
                updt.bindValue(":SUBTITLE", query.value(2).toString());
                updt.bindValue(":DESCRIPTION", query.value(3).toString());
                if (!updt.exec())
                    MythDB::DBError("Marking first showings", updt);
            }
        }
        found += query.size();
        VERBOSE(VB_GENERAL, QString("    Found %1").arg(found));

        VERBOSE(VB_GENERAL, "Marking episode last showings.");
        query.prepare("SELECT MAX(starttime),programid FROM program "
                      "WHERE programid > '' GROUP BY programid;");
        if (query.exec())
        {
            while(query.next())
            {
                updt.prepare("UPDATE program set last = 1 "
                             "WHERE starttime = :STARTTIME "
                             "  AND programid = :PROGRAMID;");
                updt.bindValue(":STARTTIME", query.value(0).toDateTime());
                updt.bindValue(":PROGRAMID", query.value(1).toString());
                if (!updt.exec())
                    MythDB::DBError("Marking last showings by id", updt);
            }
        }
        found = query.size();
        query.prepare("SELECT MAX(starttime),title,subtitle,description "
                      "FROM program WHERE programid = '' "
                      "GROUP BY title,subtitle,description;");
        if (query.exec())
        {
            while(query.next())
            {
                updt.prepare("UPDATE program set last = 1 "
                             "WHERE starttime = :STARTTIME "
                             "  AND title = :TITLE "
                             "  AND subtitle = :SUBTITLE "
                             "  AND description = :DESCRIPTION");
                updt.bindValue(":STARTTIME", query.value(0).toDateTime());
                updt.bindValue(":TITLE", query.value(1).toString());
                updt.bindValue(":SUBTITLE", query.value(2).toString());
                updt.bindValue(":DESCRIPTION", query.value(3).toString());
                if (!updt.exec())
                    MythDB::DBError("Marking last showings", updt);
            }
        }
        found += query.size();
        VERBOSE(VB_GENERAL, QString("    Found %1").arg(found));
    }

    if (1) // limit MSqlQuery's lifetime
    {
        MSqlQuery query(MSqlQuery::InitCon());
        query.prepare("SELECT count(previouslyshown) "
                      "FROM program WHERE previouslyshown = 1;");
        if (query.exec() && query.next())
        {
            if (query.value(0).toInt() != 0)
            {
                query.prepare("UPDATE settings SET data = '1' "
                              "WHERE value = 'HaveRepeats';");
                if (!query.exec())
                    MythDB::DBError("Setting HaveRepeats", query);
            }
            else
            {
                query.prepare("UPDATE settings SET data = '0' "
                              "WHERE value = 'HaveRepeats';");
                if (!query.exec())
                    MythDB::DBError("Clearing HaveRepeats", query);
            }
        }
    }

    if ((usingDataDirect) &&
        (gCoreContext->GetNumSetting("MythFillGrabberSuggestsTime", 1)))
    {
        fill_data.ddprocessor.GrabNextSuggestedTime();
    }

    VERBOSE(VB_GENERAL, "\n"
            "===============================================================\n"
            "| Attempting to contact the master backend for rescheduling.  |\n"
            "| If the master is not running, rescheduling will happen when |\n"
            "| the master backend is restarted.                            |\n"
            "===============================================================");

    if (grab_data || mark_repeats)
        ScheduledRecording::signalChange(-1);

    RemoteSendMessage("CLEAR_SETTINGS_CACHE");

    SendMythSystemEvent("MYTHFILLDATABASE_RAN");

    VERBOSE(VB_IMPORTANT, "mythfilldatabase run complete.");

    return FILLDB_EXIT_OK;
}
bool AcquireImages()
{
    // Create a GEV Device finder dialog
    PvDeviceFinderWnd lDeviceFinderWnd;

    // Prompt the user to select a GEV Device
    lDeviceFinderWnd.ShowModal();

    // Get the connectivity information for the selected GEV Device
    PvDeviceInfo* lDeviceInfo = lDeviceFinderWnd.GetSelected();

    // If no device is selected, abort
    if( lDeviceInfo == NULL )
    {
        cout << "No device selected." << endl;
        return false;
    }

    PvString lMACAddress = lDeviceInfo->GetMACAddress();
    PvString lIPAddress = lDeviceInfo->GetIPAddress();

    // Connect to the GEV Device
    PvDevice lDevice;
    cout << "Connecting to " << lMACAddress.GetAscii() << endl;
    // if ( !lDevice.Connect( lDeviceInfo ).IsOK() )
    if ( !lDevice.Connect( lDeviceInfo ).IsOK() )
    {
        cout << "Unable to connect to " << lMACAddress.GetAscii() << endl;
        return false;
    }
    cout << "Successfully connected to " << lMACAddress.GetAscii() << endl;

    cout << endl;

    SourceList lSources;

    // Get source selector
    PvGenEnum *lSourceSelector = lDevice.GetGenParameters()->GetEnum( "SourceSelector" );
    if ( lSourceSelector != NULL )
    {
        // Go through all sources, create source objects
        PvInt64 lCount = 0;
        lSourceSelector->GetEntriesCount( lCount );
        for ( PvInt64 i = 0; i < lCount; i++ )
        {
            // Get source enum entry
            const PvGenEnumEntry *lEE = NULL;
            lSourceSelector->GetEntryByIndex( i, &lEE );

            // If available, create source
            if ( ( lEE != NULL ) && lEE->IsAvailable() )
            {
                // Get source name
                PvString lSourceName;
                lEE->GetName( lSourceName );

                // Create source
                Source *lSource = new Source( &lDevice, lIPAddress, lSourceName );
                lSource->Open();

                // Add to sources list
                lSources.push_back( lSource );

                cout << endl;
            }
        }
    }
    else
    {
        // If no source selector, just create a single source
        Source *lSource = new Source( &lDevice, lIPAddress, "" );
        lSource->Open();

        // Add to sources list
        lSources.push_back( lSource );

        cout << endl;
    }

    // Start the acquisiton on all sources
    SourceList::iterator lIt = lSources.begin();
    while ( lIt != lSources.end() )
    {
        ( *( lIt++ ) )->StartAcquisition();
        cout << endl;
    }

    // Aggressive initial value, will be adjusted vs frame rate
    PvUInt32 lTimeout = 1;

    // Acquire images until the user instructs us to stop
    cout << "<press a key to stop streaming>" << endl;
    while ( !PvKbHit() )
    {
        double lNewTimeout = 1000.0;

        lIt = lSources.begin();
        while ( lIt != lSources.end() )
        {
            ( *lIt )->RetrieveImages( lTimeout );
            ( *lIt )->PrintStatistics();

            // Always use the smallest recommended timeout
            double lRecommendedTimeout = ( *lIt )->GetRecommendedTimeout();
            if ( lRecommendedTimeout < lNewTimeout )
            {
                lNewTimeout = lRecommendedTimeout;
            }

            lIt++;
        }

        // Update timeout for next round - smallest recommended divided by number of sources
        lTimeout = static_cast<PvUInt32>( lNewTimeout / static_cast<double>( lSources.size() ) + 0.5 );

        cout << "\r";
    }

    PvGetChar(); // Flush key buffer for next stop
    cout << endl << endl;

    // Stop the acquisiton on all sources
    lIt = lSources.begin();
    while ( lIt != lSources.end() )
    {
        ( *( lIt++ ) )->StopAcquisition();
        cout << endl;
    }

    // Close and delete sources
    lIt = lSources.begin();
    while ( lIt != lSources.end() )
    {
        ( *lIt )->Close();
        cout << endl;

        delete *lIt;

        lIt++;
    }

    // Finally disconnect the device. Optional, still nice to have
    cout << "Disconnecting device" << endl;
    lDevice.Disconnect();

    return true;
}