Пример #1
0
bool Content::DownloadFile( const QString &sURL, const QString &sStorageGroup )
{
    QFileInfo finfo(sURL);
    QString filename = finfo.fileName();
    StorageGroup sgroup(sStorageGroup, gCoreContext->GetHostName(), false);
    QString outDir = sgroup.FindNextDirMostFree();
    QString outFile;

    if (outDir.isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR,
            QString("Unable to determine directory "
                    "to write to in %1 write command").arg(sURL));
        return false;
    }

    if ((filename.contains("/../")) ||
        (filename.startsWith("../")))
    {
        LOG(VB_GENERAL, LOG_ERR,
            QString("ERROR: %1 write filename '%2' does not "
                    "pass sanity checks.") .arg(sURL).arg(filename));
        return false;
    }

    outFile = outDir + "/" + filename;

    if (GetMythDownloadManager()->download(sURL, outFile))
        return true;

    return false;
}
Пример #2
0
QString Content::GetHash( const QString &sStorageGroup,
                          const QString &sFileName )
{
    if ((sFileName.isEmpty()) ||
        (sFileName.contains("/../")) ||
        (sFileName.startsWith("../")))
    {
        LOG(VB_GENERAL, LOG_ERR,
            QString("ERROR checking for file, filename '%1' "
                    "fails sanity checks").arg(sFileName));
        return QString();
    }

    QString storageGroup = "Default";

    if (!sStorageGroup.isEmpty())
        storageGroup = sStorageGroup;

    StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());

    QString fullname = sgroup.FindFile(sFileName);
    QString hash = FileHash(fullname);

    if (hash == "NULL")
        return QString();

    return hash;
}
Пример #3
0
/** \fn DBUtil::GetBackupDirectory(void)
 *  \brief Determines the appropriate path for the database backup.
 *
 *   The function requests the special "DB Backups" storage group.  In the
 *   event the group is not defined, the StorageGroup will fall back to using
 *   the "Default" group.  For users upgrading from version 0.20 or before
 *   (which do not support Storage Groups), the StorageGroup will fall back to
 *   using the old RecordFilePrefix.
 */
QString DBUtil::GetBackupDirectory()
{
    QString directory;
    StorageGroup sgroup("DB Backups", gCoreContext->GetHostName());
    QStringList dirList = sgroup.GetDirList();
    if (dirList.size())
    {
        directory = sgroup.FindNextDirMostFree();

        if (!QDir(directory).exists())
        {
            LOG(VB_FILE, LOG_INFO, "GetBackupDirectory() - ignoring " +
                                   directory + ", using /tmp");
            directory = QString::null;
        }
    }

    if (directory.isNull())
        // Rather than use kDefaultStorageDir, the default for
        // FindNextDirMostFree() when no dirs are defined for the StorageGroup,
        // use /tmp as it's possible that kDefaultStorageDir doesn't exist
        // and (at least on *nix) less possible that /tmp doesn't exist
        directory = "/tmp";

    return directory;
}
Пример #4
0
HTTPLiveStream::HTTPLiveStream(QString srcFile, uint16_t width, uint16_t height,
                               uint32_t bitrate, uint32_t abitrate,
                               uint16_t maxSegments, uint16_t segmentSize,
                               uint32_t aobitrate, int32_t srate)
  : m_writing(false),
    m_streamid(-1),              m_sourceFile(srcFile),
    m_sourceWidth(0),            m_sourceHeight(0),
    m_segmentSize(segmentSize),  m_maxSegments(maxSegments),
    m_segmentCount(0),           m_startSegment(0),
    m_curSegment(0),
    m_height(height),            m_width(width),
    m_bitrate(bitrate),
    m_audioBitrate(abitrate),    m_audioOnlyBitrate(aobitrate),
    m_sampleRate(srate),
    m_created(MythDate::current()),
    m_lastModified(MythDate::current()),
    m_percentComplete(0),
    m_status(kHLSStatusUndefined)
{
    if ((m_width == 0) && (m_height == 0))
        m_width = 640;

    if (m_bitrate == 0)
        m_bitrate = 800000;

    if (m_audioBitrate == 0)
        m_audioBitrate = 64000;

    if (m_segmentSize == 0)
        m_segmentSize = 10;

    if (m_audioOnlyBitrate == 0)
        m_audioOnlyBitrate = 32000;

    m_sourceHost = gCoreContext->GetHostName();

    QFileInfo finfo(m_sourceFile);
    m_outBase = finfo.fileName() +
        QString(".%1x%2_%3kV_%4kA").arg(m_width).arg(m_height)
                .arg(m_bitrate/1000).arg(m_audioBitrate/1000);

    SetOutputVars();

    m_fullURL     = m_httpPrefix + m_outBase + ".m3u8";
    m_relativeURL = m_httpPrefixRel + m_outBase + ".m3u8";

    StorageGroup sgroup("Streaming", gCoreContext->GetHostName());
    m_outDir = sgroup.GetFirstDir();
    QDir outDir(m_outDir);

    if (!outDir.exists() && !outDir.mkdir(m_outDir))
    {
        LOG(VB_RECORD, LOG_ERR, "Unable to create HTTP Live Stream output "
            "directory, Live Stream will not be created");
        return;
    }

    AddStream();
}
Пример #5
0
void MythUIHelperPrivate::Init(void)
{
    screensaver = ScreenSaverControl::get();
    GetScreenBounds();
    StoreGUIsettings();
    screenSetup = true;

    StorageGroup sgroup("Themes", gCoreContext->GetHostName());
    m_userThemeDir = sgroup.GetFirstDir(true);
}
Пример #6
0
bool Video::AddVideo( const QString &sFilename,
                      const QString &sHost     )
{
    if ( sHost.isEmpty() )
        throw( QString( "Host not provided! Local storage is deprecated and "
                        "is not supported by the API." ));

    if ( sFilename.isEmpty() ||
        (sFilename.contains("/../")) ||
        (sFilename.startsWith("../")) )
    {
        throw( QString( "Filename not provided, or fails sanity checks!" ));
    }

    StorageGroup sgroup("Videos", sHost);

    QString fullname = sgroup.FindFile(sFilename);

    if ( !QFile::exists(fullname) )
        throw( QString( "Provided filename does not exist!" ));

    QString hash = FileHash(fullname);

    if (hash == "NULL")
    {
        VERBOSE(VB_GENERAL, QString("Video Hash Failed. Unless this is a DVD "
                                    "or Blu-ray, something has probably gone "
                                    "wrong."));
        hash = "";
    }

    VideoMetadata newFile(sFilename, hash,
                          VIDEO_TRAILER_DEFAULT,
                          VIDEO_COVERFILE_DEFAULT,
                          VIDEO_SCREENSHOT_DEFAULT,
                          VIDEO_BANNER_DEFAULT,
                          VIDEO_FANART_DEFAULT,
                          VideoMetadata::FilenameToMeta(sFilename, 1),
                          VideoMetadata::FilenameToMeta(sFilename, 4),
                          QString(), VIDEO_YEAR_DEFAULT,
                          QDate::fromString("0000-00-00","YYYY-MM-DD"),
                          VIDEO_INETREF_DEFAULT, QString(),
                          VIDEO_DIRECTOR_DEFAULT, QString(), VIDEO_PLOT_DEFAULT,
                          0.0, VIDEO_RATING_DEFAULT, 0,
                          VideoMetadata::FilenameToMeta(sFilename, 2).toInt(),
                          VideoMetadata::FilenameToMeta(sFilename, 3).toInt(),
                          QDate::currentDate(), 0, ParentalLevel::plLowest);
    newFile.SetHost(sHost);
    newFile.SaveToDatabase();

    return true;
}
Пример #7
0
Symbol *
ShadingContext::symbol (ustring name)
{
    ShaderGroup &sgroup (*attribs());
    int nlayers = sgroup.nlayers ();
    if (sgroup.llvm_compiled_version()) {
        for (int layer = nlayers-1;  layer >= 0;  --layer) {
            int symidx = sgroup[layer]->findsymbol (name);
            if (symidx >= 0)
                return sgroup[layer]->symbol (symidx);
        }
    }
    return NULL;
}
Пример #8
0
QString StorageGroup::FindFileDir(QString filename)
{
    QString result = "";
    QFileInfo checkFile("");

    int curDir = 0;
    while (curDir < m_dirlist.size())
    {
        QString testFile = m_dirlist[curDir] + "/" + filename;
        LOG(VB_FILE, LOG_DEBUG, LOC +
            QString("FindFileDir: Checking '%1' for '%2'")
                .arg(m_dirlist[curDir]).arg(testFile));
        checkFile.setFile(testFile);
        if (checkFile.exists() || checkFile.isSymLink())
        {
            QString tmp = m_dirlist[curDir];
            tmp.detach();
            return tmp;
        }

        curDir++;
    }

    if (m_groupname.isEmpty() || (m_allowFallback == false))
    {
        // Not found in any dir, so try RecordFilePrefix if it exists
        QString tmpFile =
            gCoreContext->GetSetting("RecordFilePrefix") + "/" + filename;
        checkFile.setFile(tmpFile);
        if (checkFile.exists() || checkFile.isSymLink())
            result = tmpFile;
    }
    else if (m_groupname != "Default")
    {
        // Not found in current group so try Default
        StorageGroup sgroup("Default");
        QString tmpFile = sgroup.FindFileDir(filename);
        result = (tmpFile.isEmpty()) ? result : tmpFile;
    }
    else
    {
        // Not found in Default so try any dir
        StorageGroup sgroup;
        QString tmpFile = sgroup.FindFileDir(filename);
        result = (tmpFile.isEmpty()) ? result : tmpFile;
    }

    result.detach();
    return result;
}
Пример #9
0
QStringList Content::GetFileList( const QString &sStorageGroup )
{

    if (sStorageGroup.isEmpty())
    {
        QString sMsg( "GetFileList - StorageGroup missing.");
        LOG(VB_UPNP, LOG_ERR, sMsg);

        throw sMsg;
    }

    StorageGroup sgroup(sStorageGroup);

    return sgroup.GetFileList("", true);
}
Пример #10
0
QString VideoMetadata::VideoFileHash(const QString &file_name,
                           const QString &host)
{
    if (!host.isEmpty() && !isHostMaster(host))
    {
        QString url = generate_file_url("Videos", host, file_name);
        return RemoteFile::GetFileHash(url);
    }
    else if (!host.isEmpty())
    {
        StorageGroup sgroup("Videos", host);
        QString fullname = sgroup.FindFile(file_name);
        return FileHash(fullname);
    }
    else
        return FileHash(file_name);
}
Пример #11
0
void *
ShadingContext::symbol_data (Symbol &sym)
{
    ShaderGroup &sgroup (*attribs());
    if (! sgroup.llvm_compiled_version())
        return NULL;   // can't retrieve symbol if we didn't JIT and runit

    if (sym.dataoffset() >= 0 && (int)m_heap.size() > sym.dataoffset()) {
        // lives on the heap
        return &m_heap[sym.dataoffset()];
    }

    // doesn't live on the heap
    if ((sym.symtype() == SymTypeParam || sym.symtype() == SymTypeOutputParam) &&
        (sym.valuesource() == Symbol::DefaultVal || sym.valuesource() == Symbol::InstanceVal)) {
        ASSERT (sym.data());
        return sym.data() ? sym.data() : NULL;
    }

    return NULL;  // not something we can retrieve
}
Пример #12
0
bool FileServerHandler::HandleDeleteFile(SocketHandler *socket,
                                QString filename, QString storagegroup)
{
    StorageGroup sgroup(storagegroup, "", false);
    QStringList res;

    if ((filename.isEmpty()) ||
        (filename.contains("/../")) ||
        (filename.startsWith("../")))
    {
        LOG(VB_GENERAL, LOG_ERR,
            QString("ERROR deleting file, filename '%1' fails sanity checks")
                .arg(filename));
        if (socket)
        {
            res << "0";
            socket->SendStringList(res);
            return true;
        }
        return false;
    }

    QString fullfile = sgroup.FindFile(filename);

    if (fullfile.isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR,
            QString("Unable to find %1 in HandleDeleteFile()") .arg(filename));
        if (socket)
        {
            res << "0";
            socket->SendStringList(res);
            return true;
        }
        return false;
    }

    QFile checkFile(fullfile);
    if (checkFile.exists())
    {
        if (socket)
        {
            res << "1";
            socket->SendStringList(res);
        }
        RunDeleteThread();
        deletethread->AddFile(fullfile);
    }
    else
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Error deleting file: '%1'")
                        .arg(fullfile));
        if (socket)
        {
            res << "0";
            socket->SendStringList(res);
        }
    }

    return true;
}
Пример #13
0
/**
 * \addtogroup myth_network_protocol
 * \par        QUERY_FILE_HASH \e storagegroup \e filename
 */
bool FileServerHandler::HandleQueryFileHash(SocketHandler *socket,
                                            QStringList &slist)
{
    QString storageGroup = "Default";
    QString hostname     = gCoreContext->GetHostName();
    QString filename     = "";
    QStringList res;

    switch (slist.size()) {
      case 4:
        if (!slist[3].isEmpty())
            hostname = slist[3];
      case 3:
        if (!slist[2].isEmpty())
            storageGroup = slist[2];
      case 2:
        filename = slist[1];
        if (filename.isEmpty() ||
            filename.contains("/../") ||
            filename.startsWith("../"))
        {
            LOG(VB_GENERAL, LOG_ERR,
                QString("ERROR checking for file, filename '%1' "
                        "fails sanity checks").arg(filename));
            res << "";
            socket->SendStringList(res);
            return true;
        }
        break;
      default:
        return false;
    }

    QString hash = "";

    if (hostname == gCoreContext->GetHostName())
    {
        // looking for file on me, return directly
        StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
        QString fullname = sgroup.FindFile(filename);
        hash = FileHash(fullname);
    }
    else
    {
        QReadLocker rlock(&m_fsLock);
        if (m_fsMap.contains(hostname))
        {
            // looking for file on connected host, query from it
            if (m_fsMap[hostname]->SendReceiveStringList(slist))
                hash = slist[0];
        }
        else
        {
            // looking for file on unknown host
            // assume host is an IP address, and look for matching
            // entry in database
            MSqlQuery query(MSqlQuery::InitCon());
            query.prepare("SELECT hostname FROM settings "
                           "WHERE value='BackendServerIP' "
                             " OR value='BackendServerIP6' "
                             "AND data=:HOSTNAME;");
            query.bindValue(":HOSTNAME", hostname);

            if (query.exec() && query.next())
            {
                // address matches an entry
                hostname = query.value(0).toString();
                if (m_fsMap.contains(hostname))
                {
                    // entry matches a connection
                    slist.clear();
                    slist << "QUERY_FILE_HASH"
                          << filename
                          << storageGroup;

                    if (m_fsMap[hostname]->SendReceiveStringList(slist))
                        hash = slist[0];
                }
            }
        }
    }


    res << hash;
    socket->SendStringList(res);

    return true;
}
Пример #14
0
/**
 * \addtogroup myth_network_protocol
 * \par        QUERY_FILE_EXISTS \e filename \e storagegroup
 */
bool FileServerHandler::HandleQueryFileExists(SocketHandler *socket,
                                              QStringList &slist)
{
    QString storageGroup = "Default";
    QStringList res;

    if (slist.size() == 3)
    {
        if (!slist[2].isEmpty())
            storageGroup = slist[2];
    }
    else if (slist.size() != 2)
        return false;

    QString filename = slist[1];
    if ((filename.isEmpty()) || 
        (filename.contains("/../")) || 
        (filename.startsWith("../")))
    {
        LOG(VB_GENERAL, LOG_ERR, 
            QString("ERROR checking for file, filename '%1' "
                    "fails sanity checks").arg(filename));
        res << "";
        socket->SendStringList(res);
        return true;
    }

    StorageGroup sgroup(storageGroup, gCoreContext->GetHostName());
    QString fullname = sgroup.FindFile(filename);

    if (!fullname.isEmpty())
    {
        res << "1"
            << fullname;

        // TODO: convert me to QFile
        struct stat fileinfo;
        if (stat(fullname.toLocal8Bit().constData(), &fileinfo) >= 0)
        {
            res << QString::number(fileinfo.st_dev)
                << QString::number(fileinfo.st_ino)
                << QString::number(fileinfo.st_mode)
                << QString::number(fileinfo.st_nlink)
                << QString::number(fileinfo.st_uid)
                << QString::number(fileinfo.st_gid)
                << QString::number(fileinfo.st_rdev)
                << QString::number(fileinfo.st_size)
#ifdef USING_MINGW
                << "0"
                << "0"
#else
                << QString::number(fileinfo.st_blksize)
                << QString::number(fileinfo.st_blocks)
#endif
                << QString::number(fileinfo.st_atime)
                << QString::number(fileinfo.st_mtime)
                << QString::number(fileinfo.st_ctime);
        }
    }
    else
        res << "0";

    socket->SendStringList(res);
    return true;
}
Пример #15
0
bool FileServerHandler::HandleAnnounce(MythSocket *socket,
                  QStringList &commands, QStringList &slist)
{
    if (commands[1] == "FileServer")
    {
        if (slist.size() >= 3)
        {
            SocketHandler *handler =
                new SocketHandler(socket, m_parent, commands[2]);

            handler->BlockShutdown(true);
            handler->AllowStandardEvents(true);
            handler->AllowSystemEvents(true);

            QWriteLocker wlock(&m_fsLock);
            m_fsMap.insert(commands[2], handler);
            m_parent->AddSocketHandler(handler);

            slist.clear();
            slist << "OK";
            handler->SendStringList(slist);
            return true;
        }
        return false;
    }

    if (commands[1] != "FileTransfer")
        return false;

    if (slist.size() < 3)
        return false;

    if ((commands.size() < 3) || (commands.size() > 6))
        return false;

    FileTransfer *ft    = NULL;
    QString hostname    = "";
    QString filename    = "";
    bool writemode      = false;
    bool usereadahead   = true;
    int timeout_ms      = 2000;
    switch (commands.size())
    {
      case 6:
        timeout_ms      = commands[5].toInt();
      case 5:
        usereadahead    = commands[4].toInt();
      case 4:
        writemode       = commands[3].toInt();
      default:
        hostname        = commands[2];
    }

    QStringList::const_iterator it = slist.begin();
    QUrl qurl           = *(++it);
    QString wantgroup   = *(++it);

    QStringList checkfiles;
    while (++it != slist.end())
        checkfiles += *(it);

    slist.clear();

    LOG(VB_GENERAL, LOG_DEBUG, "FileServerHandler::HandleAnnounce");
    LOG(VB_GENERAL, LOG_INFO, QString("adding: %1 as remote file transfer")
                            .arg(hostname));

    if (writemode)
    {
        if (wantgroup.isEmpty())
            wantgroup = "Default";

        StorageGroup sgroup(wantgroup, gCoreContext->GetHostName(), false);
        QString dir = sgroup.FindNextDirMostFree();
        if (dir.isEmpty())
        {
            LOG(VB_GENERAL, LOG_ERR, "Unable to determine directory "
                    "to write to in FileTransfer write command");

            slist << "ERROR" << "filetransfer_directory_not_found";
            socket->writeStringList(slist);
            return true;
        }

        QString basename = qurl.path();
        if (basename.isEmpty())
        {
            LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer write "
                    "filename is empty in url '%1'.")
                    .arg(qurl.toString()));

            slist << "ERROR" << "filetransfer_filename_empty";
            socket->writeStringList(slist);
            return true;
        }

        if ((basename.contains("/../")) ||
            (basename.startsWith("../")))
        {
            LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer write "
                    "filename '%1' does not pass sanity checks.")
                    .arg(basename));

            slist << "ERROR" << "filetransfer_filename_dangerous";
            socket->writeStringList(slist);
            return true;
        }

        filename = dir + "/" + basename;
    }
    else
        filename = LocalFilePath(qurl, wantgroup);

    QFileInfo finfo(filename);
    if (finfo.isDir())
    {
        LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer filename "
                "'%1' is actually a directory, cannot transfer.")
                .arg(filename));

        slist << "ERROR" << "filetransfer_filename_is_a_directory";
        socket->writeStringList(slist);
        return true;
    }

    if (writemode)
    {
        QString dirPath = finfo.absolutePath();
        QDir qdir(dirPath);
        if (!qdir.exists())
        {
            if (!qdir.mkpath(dirPath))
            {
                LOG(VB_GENERAL, LOG_ERR, QString("FileTransfer "
                        "filename '%1' is in a subdirectory which does "
                        "not exist, but can not be created.")
                        .arg(filename));

                slist << "ERROR" << "filetransfer_unable_to_create_subdirectory";
                socket->writeStringList(slist);
                return true;
            }
        }

        ft = new FileTransfer(filename, socket, m_parent, writemode);
    }
    else
        ft = new FileTransfer(filename, socket, m_parent, usereadahead, timeout_ms);

    ft->BlockShutdown(true);

    {
        QWriteLocker wlock(&m_ftLock);
        m_ftMap.insert(socket->socket(), ft);
    }

    slist << "OK"
          << QString::number(socket->socket())
          << QString::number(ft->GetFileSize());

    if (checkfiles.size())
    {
        QFileInfo fi(filename);
        QDir dir = fi.absoluteDir();
        for (it = checkfiles.begin(); it != checkfiles.end(); ++it)
        {
            if (dir.exists(*it) &&
                QFileInfo(dir, *it).size() >= kReadTestSize)
                    slist << *it;
        }
    }

    socket->writeStringList(slist);
    m_parent->AddSocketHandler(ft);
    return true;
}
Пример #16
0
bool FileServerHandler::HandleDownloadFile(SocketHandler *socket,
                                           QStringList &slist)
{
    QStringList res;

    if (slist.size() != 4)
    {
        res << "ERROR" << QString("Bad %1 command").arg(slist[0]);
        socket->SendStringList(res);
        return true;
    }

    bool synchronous = (slist[0] == "DOWNLOAD_FILE_NOW");
    QString srcURL = slist[1];
    QString storageGroup = slist[2];
    QString filename = slist[3];
    StorageGroup sgroup(storageGroup, gCoreContext->GetHostName(), false);
    QString outDir = sgroup.FindNextDirMostFree();
    QString outFile;
    QStringList retlist;

    if (filename.isEmpty())
    {
        QFileInfo finfo(srcURL);
        filename = finfo.fileName();
    }

    if (outDir.isEmpty())
    {
        LOG(VB_GENERAL, LOG_ERR, QString("Unable to determine directory "
                "to write to in %1 write command").arg(slist[0]));
        res << "ERROR" << "downloadfile_directory_not_found";
        socket->SendStringList(res);
        return true;
    }

    if ((filename.contains("/../")) ||
        (filename.startsWith("../")))
    {
        LOG(VB_GENERAL, LOG_ERR, QString("ERROR: %1 write "
                "filename '%2' does not pass sanity checks.")
                .arg(slist[0]).arg(filename));
        res << "ERROR" << "downloadfile_filename_dangerous";
        socket->SendStringList(res);
        return true;
    }

    outFile = outDir + "/" + filename;

    if (synchronous)
    {
        if (GetMythDownloadManager()->download(srcURL, outFile))
        {
            res << "OK"
                << gCoreContext->GetMasterHostPrefix(storageGroup)
                       + filename;
        }
        else
            res << "ERROR";
    }
    else
    {
        QMutexLocker locker(&m_downloadURLsLock);
        m_downloadURLs[outFile] =
            gCoreContext->GetMasterHostPrefix(storageGroup) +
            StorageGroup::GetRelativePathname(outFile);

        GetMythDownloadManager()->queueDownload(srcURL, outFile, this);
        res << "OK"
            << gCoreContext->GetMasterHostPrefix(storageGroup) + filename;
    }

    socket->SendStringList(res);
    return true;
}
Пример #17
0
bool UPnpCDSVideo::LoadVideos(const UPnpCDSRequest* pRequest,
                              UPnpCDSExtensionResults* pResults,
                              IDTokenMap tokens)
{
    QString sRequestId = pRequest->m_sObjectId;

    uint16_t nCount = pRequest->m_nRequestedCount;
    uint16_t nOffset = pRequest->m_nStartingIndex;

    // We must use a dedicated connection to get an acccurate value from
    // FOUND_ROWS()
    MSqlQuery query(MSqlQuery::InitCon(MSqlQuery::kDedicatedConnection));

    QString sql = "SELECT SQL_CALC_FOUND_ROWS "
                  "v.intid, title, subtitle, filename, director, plot, "
                  "rating, year, userrating, length, "
                  "season, episode, coverfile, insertdate, host, "
                  "g.genre, studio, collectionref, contenttype "
                  "FROM videometadata v "
                  "LEFT JOIN videogenre g ON g.intid=v.category "
                  "%1 " //
                  "ORDER BY title, season, episode "
                  "LIMIT :OFFSET,:COUNT ";

    QStringList clauses;
    QString whereString = BuildWhereClause(clauses, tokens);

    query.prepare(sql.arg(whereString));

    BindValues(query, tokens);

    query.bindValue(":OFFSET", nOffset);
    query.bindValue(":COUNT", nCount);

    if (!query.exec())
        return false;

    while (query.next())
    {

        int            nVidID       = query.value( 0).toInt();
        QString        sTitle       = query.value( 1).toString();
        QString        sSubtitle    = query.value( 2).toString();
        QString        sFilePath    = query.value( 3).toString();
        QString        sDirector    = query.value( 4).toString();
        QString        sPlot        = query.value( 5).toString();
        // QString        sRating      = query.value( 6).toString();
        int            nYear        = query.value( 7).toInt();
        // int             nUserRating  = query.value( 8).toInt();

        uint32_t       nLength      = query.value( 9).toUInt();
        // Convert from minutes to milliseconds
        nLength = (nLength * 60 *1000);

        int            nSeason      = query.value(10).toInt();
        int            nEpisode     = query.value(11).toInt();
        QString        sCoverArt    = query.value(12).toString();
        QDateTime      dtInsertDate =
            MythDate::as_utc(query.value(13).toDateTime());
        QString        sHostName    = query.value(14).toString();
        QString        sGenre       = query.value(15).toString();
    //    QString        sStudio      = query.value(16).toString();
    //    QString        sCollectionRef    = query.value(17).toString();
        QString        sContentType = query.value(18).toString();

        // ----------------------------------------------------------------------
        // Cache Host ip Address & Port
        // ----------------------------------------------------------------------

        // If the host-name is empty then we assume it is our local host
        // otherwise, we look up the host's IP address and port.  When the
        // client then trys to play the video it will be directed to the
        // host which actually has the content.
        if (!m_mapBackendIp.contains( sHostName ))
        {
            if (sHostName.isEmpty())
            {
                m_mapBackendIp[sHostName] =
                    gCoreContext->GetBackendServerIP4();
            }
            else
            {
                m_mapBackendIp[sHostName] =
                    gCoreContext->GetBackendServerIP4(sHostName);
            }
        }

        if (!m_mapBackendPort.contains( sHostName ))
        {
            if (sHostName.isEmpty())
            {
                m_mapBackendPort[sHostName] =
                    gCoreContext->GetBackendStatusPort();
            }
            else
            {
                m_mapBackendPort[sHostName] =
                    gCoreContext->GetBackendStatusPort(sHostName);
            }
        }


        // ----------------------------------------------------------------------
        // Build Support Strings
        // ----------------------------------------------------------------------

        QString sName      = sTitle;
        if( !sSubtitle.isEmpty() )
        {
            sName += " - " + sSubtitle;
        }

        QUrl URIBase;
        URIBase.setScheme("http");
        URIBase.setHost(m_mapBackendIp[sHostName]);
        URIBase.setPort(m_mapBackendPort[sHostName]);

        CDSObject *pItem;
        if (sContentType == "MOVIE")
        {
            pItem = CDSObject::CreateMovie( CreateIDString(sRequestId, "Video", nVidID),
                                            sTitle,
                                            pRequest->m_sParentId );
        }
        else
        {
            pItem = CDSObject::CreateVideoItem( CreateIDString(sRequestId, "Video", nVidID),
                                                sName,
                                                pRequest->m_sParentId );
        }

        if (!sSubtitle.isEmpty())
            pItem->SetPropValue( "description", sSubtitle );
        else
            pItem->SetPropValue( "description", sPlot.left(128).append(" ..."));
        pItem->SetPropValue( "longDescription", sPlot );
        pItem->SetPropValue( "director"       , sDirector );

        if (nEpisode > 0 || nSeason > 0) // There has got to be a better way
        {
            pItem->SetPropValue( "seriesTitle"  , sTitle );
            pItem->SetPropValue( "programTitle"  , sSubtitle );
            pItem->SetPropValue( "episodeNumber"  , QString::number(nEpisode));
            //pItem->SetPropValue( "episodeCount"  , nEpisodeCount);
        }

        pItem->SetPropValue( "genre"       , sGenre );
        if (nYear > 1830 && nYear < 9999)
            pItem->SetPropValue( "date",  QDate(nYear,1,1).toString(Qt::ISODate));
        else
            pItem->SetPropValue( "date", UPnPDateTime::DateTimeFormat(dtInsertDate) );

        // HACK: Windows Media Centre Compat (Not a UPnP or DLNA requirement, should only be done for WMC)
//         pItem->SetPropValue( "genre"          , "[Unknown Genre]"     );
//         pItem->SetPropValue( "actor"          , "[Unknown Author]"    );
//         pItem->SetPropValue( "creator"        , "[Unknown Creator]"   );
//         pItem->SetPropValue( "album"          , "[Unknown Album]"     );
        ////

        //pItem->SetPropValue( "producer"       , );
        //pItem->SetPropValue( "rating"         , );
        //pItem->SetPropValue( "actor"          , );
        //pItem->SetPropValue( "publisher"      , );
        //pItem->SetPropValue( "language"       , );
        //pItem->SetPropValue( "relation"       , );
        //pItem->SetPropValue( "region"         , );

        // Only add the reference ID for items which are not in the
        // 'All Videos' container
        QString sRefIDBase = QString("%1/Video").arg(m_sExtensionId);
        if ( pRequest->m_sParentId != sRefIDBase )
        {
            QString sRefId = QString( "%1=%2")
                                .arg( sRefIDBase )
                                .arg( nVidID );

            pItem->SetPropValue( "refID", sRefId );
        }

        // FIXME - If the slave or storage hosting this video is offline we
        //         won't find it. We probably shouldn't list it, but better
        //         still would be storing the filesize in the database so we
        //         don't waste time re-checking it constantly
        QString sFullFileName = sFilePath;
        if (!QFile::exists( sFullFileName ))
        {
            StorageGroup sgroup("Videos");
            sFullFileName = sgroup.FindFile( sFullFileName );
        }
        QFileInfo fInfo( sFullFileName );

        // ----------------------------------------------------------------------
        // Add Video Resource Element based on File extension (HTTP)
        // ----------------------------------------------------------------------

        QString sMimeType = HTTPRequest::GetMimeType( QFileInfo(sFilePath).suffix() );

        // HACK: If we are dealing with a Sony Blu-ray player then we fake the
        // MIME type to force the video to appear
//         if ( pRequest->m_eClient == CDS_ClientSonyDB )
//         {
//             sMimeType = "video/avi";
//         }

        QUrl    resURI    = URIBase;
        QUrlQuery resQuery;
        resURI.setPath("/Content/GetVideo");
        resQuery.addQueryItem("Id", QString::number(nVidID));
        resURI.setQuery(resQuery);

        // DLNA requires a mimetype of video/mp2p for TS files, it's not the
        // correct mimetype, but then DLNA doesn't seem to care about such
        // things
        if (sMimeType == "video/mp2t" || sMimeType == "video/mp2p")
            sMimeType = "video/mpeg";

        QString sProtocol = DLNA::ProtocolInfoString(UPNPProtocol::kHTTP,
                                                     sMimeType);

        Resource *pRes = pItem->AddResource( sProtocol, resURI.toEncoded() );
        pRes->AddAttribute( "size"    , QString("%1").arg(fInfo.size()) );
        pRes->AddAttribute( "duration", UPnPDateTime::resDurationFormat(nLength) );

        // ----------------------------------------------------------------------
        // Add Artwork
        // ----------------------------------------------------------------------
        if (!sCoverArt.isEmpty() && (sCoverArt != "No Cover"))
        {
            PopulateArtworkURIS(pItem, nVidID, URIBase);
        }

        pResults->Add( pItem );
        pItem->DecrRef();
    }

    // Just in case FOUND_ROWS() should fail, ensure m_nTotalMatches contains
    // at least the size of this result set
    if (query.size() >= 0)
        pResults->m_nTotalMatches = query.size();

    // Fetch the total number of matches ignoring any LIMITs
    query.prepare("SELECT FOUND_ROWS()");
    if (query.exec() && query.next())
            pResults->m_nTotalMatches = query.value(0).toUInt();

    return true;
}