Index: libs/libmythtv/videosource.h
===================================================================
--- libs/libmythtv/videosource.h	(revision 14206)
+++ libs/libmythtv/videosource.h	(working copy)
@@ -21,6 +21,24 @@
 class DiSEqCDevTree;
 class DiSEqCDevSettings;
 
+static inline bool is_grabber_external(const QString &grabber)
+{
+    return !(grabber == "datadirect" ||
+             grabber == "eitonly" ||
+             grabber == "schedulesdirect1" ||
+             grabber == "/bin/true");
+}
+
+static inline bool is_grabber_datadirect(const QString &grabber)
+{
+    return (grabber == "datadirect") || (grabber == "schedulesdirect1");
+}
+
+static inline bool is_grabber_labs(const QString &grabber)
+{
+    return grabber == "datadirect";
+}
+
 class VideoSourceDBStorage : public SimpleDBStorage
 {
   protected:
@@ -110,7 +128,7 @@
 {
     Q_OBJECT
   public:
-    DataDirect_config(const VideoSource& _parent, int _source = DD_ZAP2IT); 
+    DataDirect_config(const VideoSource& _parent, int _ddsource); 
 
     virtual void load(void);
 
Index: libs/libmythtv/dbcheck.cpp
===================================================================
--- libs/libmythtv/dbcheck.cpp	(revision 14206)
+++ libs/libmythtv/dbcheck.cpp	(working copy)
@@ -8,6 +8,7 @@
 
 #include "mythcontext.h"
 #include "mythdbcon.h"
+#include "datadirect.h" // for DataDirectProcessor::FixProgramIDs
 
 /// This is the DB schema version expected by the running MythTV instance.
 const QString currentDatabaseVersion = "1195";
@@ -427,6 +428,9 @@
 
     VERBOSE(VB_IMPORTANT, QString("Current Schema Version: %1").arg(dbver));
 
+    if (!gContext->GetNumSetting("MythFillFixProgramIDsHasRunOnce", 0))
+        DataDirectProcessor::FixProgramIDs();
+
     if (dbver == currentDatabaseVersion)
         return true;
 
Index: libs/libmythtv/datadirect.h
===================================================================
--- libs/libmythtv/datadirect.h	(revision 14206)
+++ libs/libmythtv/datadirect.h	(working copy)
@@ -13,8 +13,9 @@
 
 enum DD_PROVIDERS
 {
-    DD_ZAP2IT = 0,
-    DD_PROVIDER_COUNT,
+    DD_ZAP2IT           = 0,
+    DD_SCHEDULES_DIRECT = 1,
+    DD_PROVIDER_COUNT   = 2,
 };
 
 class DataDirectURLs
@@ -254,6 +255,8 @@
                         QString userid = "", QString password = "");
    ~DataDirectProcessor();
 
+    QString CreateTempDirectory(void);
+
     // web service commands
     bool GrabData(const QDateTime pstartdate, const QDateTime penddate);
     bool GrabNextSuggestedTime(void);
@@ -303,12 +306,14 @@
     RawLineup GetRawLineup( const QString &lineupid) const;
 
     // sets
-    void SetUserID(QString uid)                 { userid             = uid;  }
-    void SetPassword(QString pwd)               { password           = pwd;  }
+    void SetUserID(const QString &uid);
+    void SetPassword(const QString &pwd);
     void SetListingsProvider(uint i)
         { listings_provider = i % DD_PROVIDER_COUNT; }
-    void SetInputFile(const QString &file)      { inputfilename      = file; }
 
+    void SetInputFile(const QString &file);
+    void SetCacheData(bool cd) { cachedata = cd; }
+
     // static commands (these update temp DB tables)
     static void UpdateStationViewTable(QString lineupid);
     static void UpdateProgramViewTable(uint sourceid);
@@ -318,6 +323,9 @@
     static bool UpdateChannelsUnsafe(uint sourceid);
     static void DataDirectProgramUpdate(void);
 
+    // static command, makes Labs and Schedules Direct ProgramIDs compatible.
+    static void FixProgramIDs(void);
+
   private:
     void CreateTempTables(void);
     void CreateATempTable(const QString &ptablename,
@@ -326,6 +334,10 @@
     bool ParseLineups(const QString &documentFile);
     bool ParseLineup(const QString &lineupid, const QString &documentFile);
 
+    QString GetPostFilename(void) const;
+    QString GetResultFilename(void) const;
+    QString GetCookieFilename(void) const;
+
     void SetAll(const QString &lineupid, bool val);
     void SetDDProgramsStartAt(QDateTime begts)  { actuallistingsfrom = begts; }
     void SetDDProgramsEndAt(QDateTime endts)    { actuallistingsto   = endts; }
@@ -346,6 +358,8 @@
 
     QString       userid;
     QString       password;
+    QString       tmpDir;
+    bool          cachedata;
 
     QDateTime     actuallistingsfrom;
     QDateTime     actuallistingsto;
@@ -356,11 +370,11 @@
     DDLineupList  lineups;
     DDLineupMap   lineupmaps;
 
-    RawLineupMap  rawlineups;
-    QString       tmpPostFile;
-    QString       tmpResultFile;
-    QString       cookieFile;
-    QDateTime     cookieFileDT;
+    RawLineupMap    rawlineups;
+    mutable QString tmpPostFile;
+    mutable QString tmpResultFile;
+    mutable QString cookieFile;
+    QDateTime       cookieFileDT;
 };
 
 #endif
Index: libs/libmythtv/datadirect.cpp
===================================================================
--- libs/libmythtv/datadirect.cpp	(revision 14206)
+++ libs/libmythtv/datadirect.cpp	(working copy)
@@ -6,6 +6,7 @@
 
 // Qt headers
 #include <qmap.h>
+#include <qdir.h>
 #include <qfile.h>
 #include <qstring.h>
 #include <qregexp.h>
@@ -25,6 +26,7 @@
 #define SHOW_WGET_OUTPUT 0
 
 #define LOC QString("DataDirect: ")
+#define LOC_WARN QString("DataDirect, Warning: ")
 #define LOC_ERR QString("DataDirect, Error: ")
 
 static QMutex lineup_type_lock;
@@ -511,30 +513,12 @@
     return true;
 }
 
-static QString makeTempFile(QString name_template)
-{
-    const char *tmp = name_template.ascii();
-    char *ctemplate = strdup(tmp);
-    int ret = mkstemp(ctemplate);
-    QString tmpFileName(ctemplate);
-    free(ctemplate);
-
-    if (ret == -1)
-    {
-        VERBOSE(VB_IMPORTANT, LOC_ERR + "Creating temp file from " +
-                QString("template '%1'").arg(name_template) + ENO);
-        return name_template;
-    }
-    close(ret);
-
-    return tmpFileName;
-}
-
 DataDirectProcessor::DataDirectProcessor(uint lp, QString user, QString pass) :
     listings_provider(lp % DD_PROVIDER_COUNT),
-    userid(user),               password(pass),
-    inputfilename(""),          tmpPostFile(""),
-    tmpResultFile(""),          cookieFile(""),
+    userid(user),                   password(pass),
+    tmpDir("/tmp"),                 cachedata(false),
+    inputfilename(""),              tmpPostFile(QString::null),
+    tmpResultFile(QString::null),   cookieFile(QString::null),
     cookieFileDT()
 {
     DataDirectURLs urls0(
@@ -542,21 +526,49 @@
         "http://datadirect.webservices.zap2it.com/tvlistings/xtvdService",
         "http://labs.zap2it.com",
         "/ztvws/ztvws_login/1,1059,TMS01-1,00.html");
+    DataDirectURLs urls1(
+        "Schedules Direct",
+        "http://webservices.schedulesdirect.tmsdatadirect.com"
+        "/schedulesdirect/tvlistings/xtvdService",
+        "http://schedulesdirect.org",
+        "/login/index.php");
     providers.push_back(urls0);
-
-    QString tmpDir = "/tmp";
-    tmpPostFile   = makeTempFile(tmpDir + "/mythtv_post_XXXXXX");
-    tmpResultFile = makeTempFile(tmpDir + "/mythtv_result_XXXXXX");
-    cookieFile    = makeTempFile(tmpDir + "/mythtv_cookies_XXXXXX");
+    providers.push_back(urls1);
 }
 
 DataDirectProcessor::~DataDirectProcessor()
 {
-    unlink(tmpPostFile.ascii());
-    unlink(tmpResultFile.ascii());
-    unlink(cookieFile.ascii());
+    VERBOSE(VB_GENERAL, LOC + "Deleting temporary files");
+
+    if (!tmpPostFile.isEmpty())
+        unlink(tmpPostFile.ascii());
+
+    if (!tmpResultFile.isEmpty())
+        unlink(tmpResultFile.ascii());
+
+    if (!cookieFile.isEmpty())
+        unlink(cookieFile.ascii());
+
+    QDir d(tmpDir, "mythtv_dd_cache_*", QDir::Name,
+           QDir::Files | QDir::NoSymLinks);
+
+    for (uint i = 0; i < d.count(); i++)
+    {
+        //cout<<"deleting '"<<tmpDir<<"/"<<d[i]<<"'"<<endl;
+        unlink((tmpDir + "/" + d[i]).ascii());
+    }
+
+    if (tmpDir != "/tmp")
+        rmdir(tmpDir.ascii());
 }
 
+QString DataDirectProcessor::CreateTempDirectory(void)
+{
+    if (tmpDir == "/tmp")
+        tmpDir = createTempFile("/tmp/mythtv_ddp_XXXXXX", true);
+    return QDeepCopy<QString>(tmpDir);
+}
+
 void DataDirectProcessor::UpdateStationViewTable(QString lineupid)
 {
     MSqlQuery query(MSqlQuery::DDCon());
@@ -805,6 +817,52 @@
     //cerr << "Done...\n";
 }
 
+void DataDirectProcessor::FixProgramIDs(void)
+{
+    VERBOSE(VB_GENERAL, "DataDirectProcessor::FixProgramIDs() -- begin");
+
+    MSqlQuery query(MSqlQuery::DDCon());
+    query.prepare(
+        "UPDATE recorded "
+        "SET programid=CONCAT(SUBSTRING(programid, 1, 2), "
+        "                     '00', SUBSTRING(programid, 3)) "
+        "WHERE length(programid) = 12");
+
+    if (!query.exec())
+    {
+        MythContext::DBError("Fixing program ids in recorded", query);
+        return;
+    }
+
+    query.prepare(
+        "UPDATE oldrecorded "
+        "SET programid=CONCAT(SUBSTRING(programid, 1, 2), "
+        "                     '00', SUBSTRING(programid, 3)) "
+        "WHERE length(programid) = 12");
+
+    if (!query.exec())
+    {
+        MythContext::DBError("Fixing program ids in oldrecorded", query);
+        return;
+    }
+
+    query.prepare(
+        "UPDATE program "
+        "SET programid=CONCAT(SUBSTRING(programid, 1, 2), "
+        "                     '00', SUBSTRING(programid, 3)) "
+        "WHERE length(programid) = 12");
+
+    if (!query.exec())
+    {
+        MythContext::DBError("Fixing program ids in program", query);
+        return;
+    }
+
+    gContext->SaveSetting("MythFillFixProgramIDsHasRunOnce", "1");
+
+    VERBOSE(VB_GENERAL, "DataDirectProcessor::FixProgramIDs() -- end");
+}
+
 FILE *DataDirectProcessor::DDPost(
     QString    ddurl,
     QString    postFilename, QString    inputFile,
@@ -870,12 +928,12 @@
     VERBOSE(VB_GENERAL, "Grabbing next suggested grabbing time");
 
     QString ddurl = providers[listings_provider].webServiceURL;
-         
-    QFile postfile(tmpPostFile);
+
+    QFile postfile(GetPostFilename());
     if (!postfile.open(IO_WriteOnly))
     {
         VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Opening '%1'")
-                .arg(tmpPostFile) + ENO);
+                .arg(GetPostFilename()) + ENO);
         return false;
     }
 
@@ -896,8 +954,8 @@
 
     QString command = QString("wget --http-user='%1' --http-passwd='%2' "
                               "--post-file='%3' %4 --output-document='%5'")
-        .arg(GetUserID()).arg(GetPassword()).arg(tmpPostFile)
-        .arg(ddurl).arg(tmpResultFile);
+        .arg(GetUserID()).arg(GetPassword()).arg(GetPostFilename())
+        .arg(ddurl).arg(GetResultFilename());
 
     if (SHOW_WGET_OUTPUT)
         VERBOSE(VB_GENERAL, "command: "<<command<<endl);
@@ -909,7 +967,7 @@
     QDateTime NextSuggestedTime;
     QDateTime BlockedTime;
 
-    QFile file(tmpResultFile);
+    QFile file(GetResultFilename());
 
     bool GotNextSuggestedTime = false;
     bool GotBlockedTime = false;
@@ -994,8 +1052,25 @@
 
     QString err = "";
     QString ddurl = providers[listings_provider].webServiceURL;
+    QString inputfile = inputfilename;
+    QString cache_dd_data = QString::null;
 
-    FILE *fp = DDPost(ddurl, tmpPostFile, inputfilename,
+    if (cachedata)
+    {
+        cache_dd_data = tmpDir + QString("/mythtv_dd_cache_%1_%2_%3_%4")
+            .arg(GetListingsProvider())
+            .arg(GetUserID().ascii())
+            .arg(pstartDate.toString())
+            .arg(pendDate.toString());
+
+        if (QFile(cache_dd_data).exists() && inputfilename.isEmpty())
+        {
+            VERBOSE(VB_GENERAL, LOC + "Copying from DD cache");
+            inputfile = cache_dd_data;
+        }
+    }
+
+    FILE *fp = DDPost(ddurl, GetPostFilename(), inputfile,
                       GetUserID(), GetPassword(),
                       pstartDate, pendDate, err);
     if (!fp)
@@ -1005,6 +1080,44 @@
         return false;
     }
 
+    if (cachedata && (inputfile != cache_dd_data))
+    {
+        QFile in, out(cache_dd_data);
+        bool ok = out.open(IO_WriteOnly);
+        if (!ok)
+        {
+            VERBOSE(VB_IMPORTANT, LOC_WARN +
+                    "Can not open DD cache file in '" +
+                    tmpDir + "' for writing!");
+        }
+        else
+        {
+            VERBOSE(VB_GENERAL, LOC + "Saving listings to DD cache");
+            ok = in.open(IO_ReadOnly, fp);
+            out.close(); // let copy routine handle dst file
+        }
+
+        if (ok)
+        {
+            if (copy(out, in))
+            {
+                pclose(fp);
+                fp = fopen(cache_dd_data.ascii(), "r");
+            }
+            else
+            {
+                VERBOSE(VB_IMPORTANT,
+                        LOC_ERR + "Failed to save DD cache! "
+                        "redownloading data...");
+                cachedata = false;
+                pclose(fp);
+                fp = DDPost(ddurl, GetPostFilename(), inputfile,
+                            GetUserID(), GetPassword(),
+                            pstartDate, pendDate, err);
+            }
+        }
+    }
+
     QFile f;
     if (f.open(IO_ReadOnly, fp)) 
     {
@@ -1083,7 +1196,7 @@
         "  channelMinor char(3) )";
 
     dd_tables["dd_schedule"] =
-        "( programid char(12),           stationid char(12), "
+        "( programid char(40),           stationid char(12), "
         "  scheduletime datetime,        duration time,      "
         "  isrepeat bool,                stereo bool,        "
         "  subtitled bool,               hdtv bool,          "
@@ -1093,7 +1206,7 @@
         "INDEX progidx (programid) )";
 
     dd_tables["dd_program"] =
-        "( programid char(12) NOT NULL,  seriesid char(12),     "
+        "( programid char(40) NOT NULL,  seriesid char(12),     "
         "  title varchar(120),           subtitle varchar(150), "
         "  description text,             mpaarating char(5),    "
         "  starrating char(5),           runtime time,          "
@@ -1115,19 +1228,19 @@
         "  partnumber int,               parttotal int,               "
         "  seriesid char(12),            originalairdate date,        "
         "  showtype varchar(30),         colorcode varchar(20),       "
-        "  syndicatedepisodenumber varchar(20), programid char(12),   "
+        "  syndicatedepisodenumber varchar(20), programid char(40),   "
         "  tvrating char(5),             mpaarating char(5),          "
         "INDEX progidx (programid))";
 
     dd_tables["dd_productioncrew"] =
-        "( programid char(12),           role char(30),    "
+        "( programid char(40),           role char(30),    "
         "  givenname char(20),           surname char(20), "
         "  fullname char(41), "
         "INDEX progidx (programid), "
         "INDEX nameidx (fullname))";
 
     dd_tables["dd_genre"] =
-        "( programid char(12) NOT NULL,  class char(30), "
+        "( programid char(40) NOT NULL,  class char(30), "
         "  relevance char(1), "
         "INDEX progidx (programid))";
 
@@ -1148,11 +1261,12 @@
     QString labsURL   = providers[listings_provider].webURL;
     QString loginPage = providers[listings_provider].loginPage;
 
-    bool ok = Post(labsURL + loginPage, list, tmpResultFile, "", cookieFile);
+    bool ok = Post(labsURL + loginPage, list, GetResultFilename(), "",
+                   GetCookieFilename());
 
-    bool got_cookie = QFileInfo(cookieFile).size() > 100;
+    bool got_cookie = QFileInfo(GetCookieFilename()).size() > 100;
 
-    ok &= got_cookie && (!parse_lineups || ParseLineups(tmpResultFile));
+    ok &= got_cookie && (!parse_lineups || ParseLineups(GetResultFilename()));
     if (ok)
         cookieFileDT = QDateTime::currentDateTime();
 
@@ -1175,10 +1289,10 @@
     list.push_back(PostItem("submit",    "Modify"));
 
     QString labsURL = providers[listings_provider].webURL;
-    bool ok = Post(labsURL + (*it).get_action, list, tmpResultFile,
-                   cookieFile, "");
+    bool ok = Post(labsURL + (*it).get_action, list, GetResultFilename(),
+                   GetCookieFilename(), "");
 
-    return ok && ParseLineup(lineupid, tmpResultFile);
+    return ok && ParseLineup(lineupid, GetResultFilename());
 }
 
 void DataDirectProcessor::SetAll(const QString &lineupid, bool val)
@@ -1457,7 +1571,8 @@
             .arg(lineupid).arg(list.size() - 1));
 
     QString labsURL = providers[listings_provider].webURL;
-    return Post(labsURL + lineup.set_action, list, "", cookieFile, "");
+    return Post(labsURL + lineup.set_action, list, "",
+                GetCookieFilename(), "");
 }
 
 bool DataDirectProcessor::UpdateListings(uint sourceid)
@@ -1534,6 +1649,42 @@
     return (*it);
 }
 
+QString DataDirectProcessor::GetPostFilename(void) const
+{
+    if (tmpPostFile.isEmpty())
+        tmpPostFile = createTempFile(tmpDir + "/mythtv_post_XXXXXX");
+    return QDeepCopy<QString>(tmpPostFile);
+}
+
+QString DataDirectProcessor::GetResultFilename(void) const
+{
+    if (tmpResultFile.isEmpty())
+        tmpResultFile = createTempFile(tmpDir + "/mythtv_result_XXXXXX");
+    return QDeepCopy<QString>(tmpResultFile);
+}
+
+QString DataDirectProcessor::GetCookieFilename(void) const
+{
+    if (cookieFile.isEmpty())
+        cookieFile = createTempFile(tmpDir + "/mythtv_cookies_XXXXXX");
+    return QDeepCopy<QString>(cookieFile);
+}
+
+void DataDirectProcessor::SetUserID(const QString &uid)
+{
+    userid = QDeepCopy<QString>(uid);
+}
+
+void DataDirectProcessor::SetPassword(const QString &pwd)
+{
+    password = QDeepCopy<QString>(pwd);
+}
+
+void DataDirectProcessor::SetInputFile(const QString &file)
+{
+    inputfilename = QDeepCopy<QString>(file);
+}
+
 bool DataDirectProcessor::Post(QString url, const PostList &list,
                                QString documentFile,
                                QString inCookieFile, QString outCookieFile)
Index: libs/libmythtv/videosource.cpp
===================================================================
--- libs/libmythtv/videosource.cpp	(revision 14206)
+++ libs/libmythtv/videosource.cpp	(working copy)
@@ -20,6 +20,7 @@
 #include <qmap.h>
 #include <qdir.h>
 #include <qprocess.h>
+#include <qdatetime.h>
 
 // MythTV headers
 #include "mythconfig.h"
@@ -295,8 +296,10 @@
 {
   public:
     DataDirectPassword(const VideoSource &parent) :
-        LineEditSetting(this), VideoSourceDBStorage(this, parent, "password")
+        LineEditSetting(this, true),
+        VideoSourceDBStorage(this, parent, "password")
     {
+        SetPasswordEcho(true);
         setLabel(QObject::tr("Password"));
     }
 };
@@ -307,7 +310,6 @@
 {
     (void) uid;
     (void) pwd;
-    (void) _source;
 #ifdef USING_BACKEND
     if (uid.isEmpty() || pwd.isEmpty())
         return;
@@ -348,8 +350,11 @@
 void DataDirect_config::load() 
 {
     VerticalConfigurationGroup::load();
-    if ((userid->getValue() != lastloadeduserid) || 
-        (password->getValue() != lastloadedpassword)) 
+    bool is_sd_userid = userid->getValue().contains("@") > 0;
+    bool match = ((is_sd_userid  && (source == DD_SCHEDULES_DIRECT)) ||
+                  (!is_sd_userid && (source == DD_ZAP2IT)));
+    if (((userid->getValue() != lastloadeduserid) ||
+         (password->getValue() != lastloadedpassword)) && match)
     {
         lineupselector->fillSelections(userid->getValue(), 
                                        password->getValue(),
@@ -442,8 +447,7 @@
         "instead of just 'mythfilldatabase'.\nYour grabber does not provide "
         "channel numbers, so you have to set them manually.");
 
-    if (grabber != "datadirect" && grabber != "eitonly" && 
-        grabber != "/bin/true")
+    if (is_grabber_external(grabber))
     {
         VERBOSE(VB_IMPORTANT, "\n" << err_msg);
         MythPopupBox::showOkPopup(
@@ -508,9 +512,17 @@
     // only save settings for the selected grabber
     setSaveAll(false);
 
-    addTarget("datadirect", new DataDirect_config(parent));
-    grabber->addSelection("North America (DataDirect) (Internal)", "datadirect");
+    addTarget("schedulesdirect1",
+              new DataDirect_config(parent, DD_SCHEDULES_DIRECT));
+    grabber->addSelection("North America (SchedulesDirect.org) "
+                          "(Internal)", "schedulesdirect1");
 
+#if 1
+    addTarget("datadirect", new DataDirect_config(parent, DD_ZAP2IT));
+    grabber->addSelection(
+        "North America (TMS Labs) (Internal)", "datadirect");
+#endif
+
     addTarget("eitonly", new EITOnly_config(parent));
     grabber->addSelection("Transmitted guide only (EIT)", "eitonly");
 
Index: libs/libmyth/settings.cpp
===================================================================
--- libs/libmyth/settings.cpp	(revision 14206)
+++ libs/libmyth/settings.cpp	(working copy)
@@ -639,7 +639,8 @@
         connect(edit, SIGNAL(changeHelpText(QString)), cg, 
                 SIGNAL(changeHelpText(QString)));
 
-    edit->setRW(rw);
+    setRW(rw);
+    SetPasswordEcho(password_echo);
 
     return widget;
 }
@@ -664,6 +665,13 @@
     }
 }
 
+void LineEditSetting::SetPasswordEcho(bool b)
+{
+    password_echo = b;
+    if (edit)
+        edit->setEchoMode(b ? QLineEdit::Password : QLineEdit::Normal);
+}
+
 QWidget* SliderSetting::configWidget(ConfigurationGroup *cg, QWidget* parent,
                                      const char* widgetName) {
     QHBox* widget;
Index: libs/libmyth/util.cpp
===================================================================
--- libs/libmyth/util.cpp	(revision 14206)
+++ libs/libmyth/util.cpp	(working copy)
@@ -32,6 +32,7 @@
 #include <qpainter.h>
 #include <qpixmap.h>
 #include <qfont.h>
+#include <qfile.h>
 
 // Myth headers
 #include "mythconfig.h"
@@ -603,3 +604,107 @@
 
     return false;
 }
+
+/** \fn  Copy(QFile&,QFile&,uint)
+ *  \brief Copies src file to dst file.
+ *
+ *   If the dst file is open, it must be open for writing.
+ *   If the src file is open, if must be open for reading.
+ *
+ *   The files will be in the same open or close state after
+ *   this function runs as they were prior to this function being called.
+ *
+ *   This function does not care if the files are actual files.
+ *   For compatibility with pipes and socket streams the file location
+ *   will not be reset to 0 at the end of this function. If the function
+ *   is succesful the file pointers will be at the end of the copied
+ *   data.
+ *
+ *  \param dst Destination QFile
+ *  \param src Source QFile
+ *  \param block_size Optional block size in bytes, must be at least 1024,
+ *                    otherwise the default of 16 KB will be used.
+ *  \return bytes copied on success, -1 on failure.
+ */
+long long copy(QFile &dst, QFile &src, uint block_size)
+{
+    uint buflen = (block_size < 1024) ? (16 * 1024) : block_size;
+    char *buf = new char[buflen];
+    bool odst = false, osrc = false;
+
+    if (!buf)
+        return -1LL;
+
+    if (!dst.isWritable() && !dst.isOpen())
+        odst = dst.open(IO_Raw|IO_WriteOnly|IO_Truncate);
+
+    if (!src.isReadable() && !src.isOpen())
+        osrc = src.open(IO_Raw|IO_ReadOnly);
+
+    bool ok = dst.isWritable() && src.isReadable();
+    long long total_bytes = 0LL;
+    while (ok)
+    {
+        long long rlen, wlen, off = 0;
+        rlen = src.readBlock(buf, buflen);
+        if (rlen<0)
+        {
+            ok = false;
+            break;
+        }
+        if (rlen==0)
+            break;
+
+        total_bytes += (long long) rlen;
+
+        while ((rlen-off>0) && ok)
+        {
+            wlen = dst.writeBlock(buf + off, rlen - off);
+            if (wlen>=0)
+                off+= wlen;
+            if (wlen<0)
+                ok = false;
+        }
+    }
+    delete[] buf;
+
+    if (odst)
+        dst.close();
+
+    if (osrc)
+        src.close();
+
+    return (ok) ? total_bytes : -1LL;
+}
+
+QString createTempFile(QString name_template, bool dir)
+{
+    const char *tmp = name_template.ascii();
+    char *ctemplate = strdup(tmp);
+    int ret = -1;
+
+    if (dir)
+    {
+        ret = (mkdtemp(ctemplate)) ? 0 : -1;
+    }
+    else
+    {
+        ret = mkstemp(ctemplate);
+    }
+
+    QString tmpFileName(ctemplate);
+    free(ctemplate);
+
+    if (ret == -1)
+    {
+        VERBOSE(VB_IMPORTANT, QString("createTempFile(%1), Error ")
+                .arg(name_template) + ENO);
+        return name_template;
+    }
+
+    if (!dir && (ret >= 0))
+        close(ret);
+
+    return tmpFileName;
+}
+
Index: libs/libmyth/util.h
===================================================================
--- libs/libmyth/util.h	(revision 14206)
+++ libs/libmyth/util.h	(working copy)
@@ -16,6 +16,7 @@
 class QImage;
 class QPainter;
 class QFont;
+class QFile;
 
 class MPUBLIC MythTimer
 {
@@ -73,4 +74,8 @@
 MPUBLIC bool ping(const QString &host, int timeout);
 MPUBLIC bool telnet(const QString &host, int port);
 
+MPUBLIC long long copy(QFile &dst, QFile &src, uint block_size = 0);
+MPUBLIC QString createTempFile(QString name_template = "/tmp/mythtv_XXXXX",
+                               bool dir = false);
+
 #endif // UTIL_H_
Index: libs/libmyth/mythcontext.h
===================================================================
--- libs/libmyth/mythcontext.h	(revision 14206)
+++ libs/libmyth/mythcontext.h	(working copy)
@@ -208,7 +208,7 @@
 
 /// Update this whenever the plug-in API changes.
 /// Including changes in the libmythtv class methods used by plug-ins.
-#define MYTH_BINARY_VERSION "0.20.20070717-1"
+#define MYTH_BINARY_VERSION "0.20.20070817-1"
 
 /** \brief Increment this whenever the MythTV network protocol changes.
  *
Index: libs/libmyth/settings.h
===================================================================
--- libs/libmyth/settings.h	(revision 14206)
+++ libs/libmyth/settings.h	(working copy)
@@ -200,7 +200,7 @@
 {
   protected:
     LineEditSetting(Storage *_storage, bool readwrite = true) :
-        Setting(_storage), edit(NULL), rw(readwrite) { }
+        Setting(_storage), edit(NULL), rw(readwrite), password_echo(false) { }
 
   public:
     virtual QWidget* configWidget(ConfigurationGroup *cg, QWidget* parent, 
@@ -217,10 +217,12 @@
 
     virtual void setEnabled(bool b);
     virtual void setVisible(bool b);
+    virtual void SetPasswordEcho(bool b);
 
 private:
     MythLineEdit* edit;
     bool rw;
+    bool password_echo;
 };
 
 // TODO: set things up so that setting the value as a string emits
Index: programs/mythfilldatabase/filldata.cpp
===================================================================
--- programs/mythfilldatabase/filldata.cpp	(revision 14206)
+++ programs/mythfilldatabase/filldata.cpp	(working copy)
@@ -23,6 +23,9 @@
 #include "mythcontext.h"
 #include "mythdbcon.h"
 
+// libmythtv headers
+#include "videosource.h" // for is_grabber..
+
 // filldata headers
 #include "filldata.h"
 
@@ -44,7 +47,8 @@
         icon_data.UpdateSourceIcons(source.id);
 
     // Unselect channels not in users lineup for DVB, HDTV
-    if (!insert_channels && (new_channels > 0))
+    if (!insert_channels && (new_channels > 0) &&
+        is_grabber_labs(source.xmltvgrabber))
     {
         bool ok0 = (logged_in == source.userid);
         bool ok1 = (raw_lineup == source.id);
@@ -69,6 +73,13 @@
 
 bool FillData::DataDirectUpdateChannels(Source source)
 {
+    if (!is_grabber_labs(source.xmltvgrabber))
+    {
+        VERBOSE(VB_IMPORTANT, "FillData: We only support "
+                "DataDirectUpdateChannels with TMS Labs channel editor");
+        return false;
+    }
+
     ddprocessor.SetListingsProvider(DD_ZAP2IT);
     ddprocessor.SetUserID(source.userid);
     ddprocessor.SetPassword(source.password);
@@ -87,6 +98,25 @@
 bool FillData::grabDDData(Source source, int poffset,
                           QDate pdate, int ddSource) 
 {
+    if (source.dd_dups.empty())
+        ddprocessor.SetCacheData(false);
+    else
+    {
+        VERBOSE(VB_GENERAL, QString(
+                    "This DataDirect listings source is "
+                    "shared by %1 MythTV lineups")
+                .arg(source.dd_dups.size()+1));
+        if (source.id > source.dd_dups[0])
+        {
+            VERBOSE(VB_IMPORTANT, "We should use cached data for this one");
+        }
+        else if (source.id < source.dd_dups[0])
+        {
+            VERBOSE(VB_IMPORTANT, "We should keep data around after this one");
+        }
+        ddprocessor.SetCacheData(true);
+    }
+
     ddprocessor.SetListingsProvider(ddSource);
     ddprocessor.SetUserID(source.userid);
     ddprocessor.SetPassword(source.password);
@@ -251,6 +281,8 @@
 
     if (xmltv_grabber == "datadirect")
         return grabDDData(source, offset, *qCurrentDate, DD_ZAP2IT);
+    if (xmltv_grabber == "schedulesdirect1")
+        return grabDDData(source, offset, *qCurrentDate, DD_SCHEDULES_DIRECT);
 
     char tempfilename[] = "/tmp/mythXXXXXX";
     if (mkstemp(tempfilename) == -1)
@@ -389,6 +421,7 @@
 bool FillData::fillData(QValueList<Source> &sourcelist)
 {
     QValueList<Source>::Iterator it;
+    QValueList<Source>::Iterator it2;
 
     QString status, querystr;
     MSqlQuery query(MSqlQuery::InitCon());
@@ -402,10 +435,32 @@
 
     need_post_grab_proc = false;
     int nonewdata = 0;
+    bool has_dd_source = false;
 
+    // find all DataDirect duplicates, so we only data download once.
     for (it = sourcelist.begin(); it != sourcelist.end(); ++it)
     {
+        if (!is_grabber_datadirect((*it).xmltvgrabber))
+            continue;
 
+        has_dd_source = true;
+        for (it2 = sourcelist.begin(); it2 != sourcelist.end(); ++it2)
+        {
+            if (((*it).id           != (*it2).id)           &&
+                ((*it).xmltvgrabber == (*it2).xmltvgrabber) &&
+                ((*it).userid       == (*it2).userid)       &&
+                ((*it).password     == (*it2).password))
+            {
+                (*it).dd_dups.push_back((*it2).id);
+            }
+        }
+    }
+    if (has_dd_source)
+        ddprocessor.CreateTempDirectory();
+
+    for (it = sourcelist.begin(); it != sourcelist.end(); ++it)
+    {
+
         query.prepare("SELECT MAX(endtime) FROM program p LEFT JOIN channel c "
                       "ON p.chanid=c.chanid WHERE c.sourceid= :SRCID "
                       "AND manualid = 0;");
@@ -427,8 +482,11 @@
 
         if (xmltv_grabber == "eitonly")
         {
-            VERBOSE(VB_IMPORTANT, "Source configured to use only the "
-                    "broadcasted guide data. Skipping.");
+            VERBOSE(VB_GENERAL,
+                    QString("Source %1 configured to use only the "
+                            "broadcasted guide data. Skipping.")
+                    .arg((*it).id));
+
             externally_handled++;
             query.exec(QString("UPDATE settings SET data ='%1' "
                                "WHERE value='mythfilldatabaseLastRunStart' OR "
@@ -440,8 +498,10 @@
                  xmltv_grabber == "none" ||
                  xmltv_grabber == "")
         {
-            VERBOSE(VB_IMPORTANT,
-                    "Source configured with no grabber. Nothing to do.");
+            VERBOSE(VB_GENERAL,
+                    QString("Source %1 configured with no grabber. "
+                            "Nothing to do.").arg((*it).id));
+
             externally_handled++;
             query.exec(QString("UPDATE settings SET data ='%1' "
                                "WHERE value='mythfilldatabaseLastRunStart' OR "
@@ -486,7 +546,7 @@
 
         bool hasprefmethod = false;
 
-        if (xmltv_grabber != "datadirect")
+        if (is_grabber_external(xmltv_grabber))
         {
 
             QProcess grabber_capabilities_proc(xmltv_grabber);
@@ -584,9 +644,9 @@
             }
         }
 
-        need_post_grab_proc |= (xmltv_grabber != "datadirect");
+        need_post_grab_proc |= !is_grabber_datadirect(xmltv_grabber);
 
-        if ((xmltv_grabber == "datadirect") && dd_grab_all)
+        if (is_grabber_labs(xmltv_grabber) && dd_grab_all)
         {
             if (only_update_channels)
                 DataDirectUpdateChannels(*it);
@@ -601,7 +661,8 @@
             if (!grabData(*it, 0))
                 ++failures;
         }
-        else if ((*it).xmltvgrabber_baseline || xmltv_grabber == "datadirect")
+        else if ((*it).xmltvgrabber_baseline ||
+                 is_grabber_datadirect(xmltv_grabber))
         {
 
             QDate qCurrentDate = QDate::currentDate();
@@ -612,7 +673,7 @@
 
             if (maxDays > 0) // passed with --max-days
                 grabdays = maxDays;
-            else if (xmltv_grabber == "datadirect")
+            else if (is_grabber_datadirect(xmltv_grabber))
                 grabdays = 14;
 
             grabdays = (only_update_channels) ? 1 : grabdays;
@@ -620,7 +681,7 @@
             if (grabdays == 1)
                 refresh_today = true;
 
-            if ((xmltv_grabber == "datadirect") && only_update_channels)
+            if (is_grabber_labs(xmltv_grabber) && only_update_channels)
             {
                 DataDirectUpdateChannels(*it);
                 grabdays = 0;
Index: programs/mythfilldatabase/main.cpp
===================================================================
--- programs/mythfilldatabase/main.cpp	(revision 14206)
+++ programs/mythfilldatabase/main.cpp	(working copy)
@@ -16,6 +16,7 @@
 // libmythtv headers
 #include "scheduledrecording.h"
 #include "remoteutil.h"
+#include "videosource.h" // for is_grabber..
 
 // filldata headers
 #include "filldata.h"
@@ -34,7 +35,7 @@
     bool from_file = false;
     bool mark_repeats = true;
 
-    bool usingDataDirect = false;
+    bool usingDataDirect = false, usingDataDirectLabs = false;
     bool grab_data = true;
 
     bool export_iconmap = false;
@@ -579,8 +580,10 @@
                        newsource.xmltvgrabber_prefmethod = "";
 
                        sourcelist.append(newsource);
-                       if (newsource.xmltvgrabber == "datadirect")
-                           usingDataDirect = true;
+                       usingDataDirect |=
+                           is_grabber_datadirect(newsource.xmltvgrabber);
+                       usingDataDirectLabs |=
+                           is_grabber_labs(newsource.xmltvgrabber);
                   }
              }
              else
@@ -814,6 +817,12 @@
         fill_data.ddprocessor.GrabNextSuggestedTime();
     }
 
+    if (usingDataDirectLabs ||
+        !gContext->GetNumSetting("MythFillFixProgramIDsHasRunOnce", 0))
+    {
+        DataDirectProcessor::FixProgramIDs();
+    }
+
     VERBOSE(VB_GENERAL, "\n"
             "===============================================================\n"
             "| Attempting to contact the master backend for rescheduling.  |\n"
Index: programs/mythfilldatabase/filldata.h
===================================================================
--- programs/mythfilldatabase/filldata.h	(revision 14206)
+++ programs/mythfilldatabase/filldata.h	(working copy)
@@ -26,6 +26,7 @@
     bool    xmltvgrabber_manualconfig;
     bool    xmltvgrabber_cache;
     QString xmltvgrabber_prefmethod;
+    vector<int> dd_dups;
 };
 
 class FillData
