Index: libs/libmyth/storagegroup.h
===================================================================
--- libs/libmyth/storagegroup.h	(revision 19997)
+++ libs/libmyth/storagegroup.h	(working copy)
@@ -9,17 +9,37 @@
 class MPUBLIC StorageGroup: public ConfigurationWizard
 {
   public:
-    StorageGroup(const QString group = "", const QString hostname = "");
+    StorageGroup(const QString group = "",
+                 const QString hostname = "",
+                 bool useDefaultWhenEmpty = true);
 
     void    Init(const QString group = "Default",
-                 const QString hostname = "");
+                 const QString hostname = "",
+                 bool useDefaultWhenEmpty = true);
 
+    void    Delete(void);
+
+    virtual void Save(void);
+
+    bool hasChanged(void);
+
+    QString AddDir(const QString directory);
+    QString DelDir(const QString directory);
+
     QString getName(void) const
         { QString tmp = m_groupname; tmp.detach(); return tmp; }
 
     QStringList GetDirList(void) const
         { QStringList tmp = m_dirlist; tmp.detach(); return tmp; }
 
+    typedef QMap<QString, QStringList> FilesList;
+
+    FilesList GetAllFiles(bool descendIntoSubDirs = false,
+                          bool followSymbolicLinks = true);
+    static QStringList GetAllFiles(const QString directory,
+                                   bool descendIntoSubDirs = false,
+                                   bool followSymbolicLinks = true);
+
     QStringList GetFileList(QString Path);
     bool FileExists(QString filename);
     QStringList GetFileInfo(QString filename);
@@ -29,6 +49,9 @@
 
     QString FindNextDirMostFree(void);
 
+    static QStringList GetStorageGroupNames(const QString hostname = "");
+    static QString FindStorageGroupForDir(const QString directory, const QString hostname = "");
+
     static void CheckAllStorageGroupDirs(void);
 
     static const char *kDefaultStorageDir;
@@ -40,7 +63,14 @@
   private:
     QString      m_groupname;
     QString      m_hostname;
+    QStringList  m_originaldirs;
     QStringList  m_dirlist;
+
+    static void GetAllFilesImpl(const QString directory,
+                                QStringList& fileList,
+                                bool descendIntoSubDirs = false,
+                                bool followSymbolicLinks = true,
+                                const QString prefix = "");
 };
 
 class MPUBLIC StorageGroupEditor :
@@ -49,19 +79,29 @@
     Q_OBJECT
   public:
     StorageGroupEditor(QString group);
+    virtual ~StorageGroupEditor(void);
     virtual DialogCode exec(void);
     virtual void Load(void);
-    virtual void Save(void) { }
+    virtual void Save(void);
     virtual void Save(QString) { }
+    void Delete();
+    bool hasChanged(void);
     virtual MythDialog* dialogWidget(MythMainWindow* parent,
                                      const char* widgetname=0);
 
+    QString getGroup(void) const
+    {
+        QString groupName;
+        groupName = m_group->getName();
+        return groupName;
+    }
+
   protected slots:
     void open(QString name);
     void doDelete(void);
 
   protected:
-    QString         m_group;
+    StorageGroup   *m_group;
     ListBoxSetting *listbox;
     QString         lastValue;
 };
@@ -72,10 +112,12 @@
     Q_OBJECT
   public:
     StorageGroupListEditor(void);
+    virtual ~StorageGroupListEditor(void);
     virtual DialogCode exec(void);
     virtual void Load(void);
-    virtual void Save(void) { }
+    virtual void Save(void);
     virtual void Save(QString) { }
+    bool hasChanged(void);
     virtual MythDialog* dialogWidget(MythMainWindow* parent,
                                      const char* widgetname=0);
 
@@ -86,6 +128,10 @@
   protected:
     ListBoxSetting *listbox;
     QString         lastValue;
+
+    typedef QMap<QString, StorageGroupEditor*> StorageGroupEditors;
+    StorageGroupEditors m_edited;
+    StorageGroupEditors m_deleted;
 };
 
 #endif
Index: libs/libmyth/storagegroup.cpp
===================================================================
--- libs/libmyth/storagegroup.cpp	(revision 19997)
+++ libs/libmyth/storagegroup.cpp	(working copy)
@@ -4,8 +4,8 @@
 
 #include "storagegroup.h"
 #include "mythcontext.h"
-#include "mythdb.h"
-#include "mythverbose.h"
+#include "libmythdb/mythdb.h"
+#include "libmythdb/mythverbose.h"
 #include "util.h"
 
 #define LOC QString("SG(%1): ").arg(m_groupname)
@@ -28,18 +28,25 @@
  *  \param hostname hostname where to search, blank will search all hosts'
  *                  directories, but only in local directory structure.
  *                  This is parameter is ignored if group is an empty string.
+ *  \parm useDefaultWhenEmpty fall back to directories in the "Default" group
+ *                  when no directories can be found for a given group/host
+ *                  combination. Defaults to true. Clients doing programmatic
+ *                  manipulation of storage groups should pass false.
  */
-StorageGroup::StorageGroup(const QString group, const QString hostname) :
+StorageGroup::StorageGroup(const QString group, const QString hostname,
+                           bool useDefaultWhenEmpty) :
     m_groupname(group), m_hostname(hostname)
 {
     m_groupname.detach();
     m_hostname.detach();
     m_dirlist.clear();
+    m_originaldirs.clear();
 
-    Init(m_groupname, m_hostname);
+    Init(m_groupname, m_hostname, useDefaultWhenEmpty);
 }
 
-void StorageGroup::Init(const QString group, const QString hostname)
+void StorageGroup::Init(const QString group, const QString hostname,
+                        bool useDefaultWhenEmpty)
 {
     QString dirname;
     MSqlQuery query(MSqlQuery::InitCon());
@@ -70,23 +77,26 @@
         MythDB::DBError("StorageGroup::StorageGroup()", query);
     else if (!query.next())
     {
-        if (group != "Default")
+        if (useDefaultWhenEmpty)
         {
-            VERBOSE(VB_FILE, LOC +
-                    QString("Unable to find storage group '%1', trying "
-                            "'Default' group!").arg(m_groupname));
-            Init("Default", m_hostname);
-            return;
+            if (group != "Default")
+            {
+                VERBOSE(VB_FILE, LOC +
+                        QString("Unable to find storage group '%1', trying "
+                                "'Default' group!").arg(m_groupname));
+                Init("Default", m_hostname);
+                return;
+            }
+            else if (!m_hostname.isEmpty())
+            {
+                VERBOSE(VB_FILE, LOC +
+                        QString("Unable to find any directories for the local "
+                                "Default storage group, trying directories in all "
+                                "Default groups!").arg(m_groupname));
+                Init("Default", "");
+                return;
+            }
         }
-        else if (!m_hostname.isEmpty())
-        {
-            VERBOSE(VB_FILE, LOC +
-                    QString("Unable to find any directories for the local "
-                            "Default storage group, trying directories in all "
-                            "Default groups!").arg(m_groupname));
-            Init("Default", "");
-            return;
-        }
     }
     else
     {
@@ -102,7 +112,9 @@
         while (query.next());
     }
 
-    if (!m_dirlist.size())
+    m_originaldirs = m_dirlist;
+
+    if (!m_dirlist.size() && group == "Default")
     {
         QString msg = "Directory value for Default Storage Group is empty.  ";
         QString tmpDir = gContext->GetSetting("RecordFilePrefix");
@@ -122,6 +134,211 @@
     }
 }
 
+/** \brief Remove all host-specific database entries for the Storage Group
+ */
+void StorageGroup::Delete(void)
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare("DELETE FROM storagegroup "
+                  "WHERE groupname = :NAME AND hostname = :HOSTNAME;");
+    query.bindValue(":NAME", m_groupname);
+    query.bindValue(":HOSTNAME", m_hostname);
+    if (!query.exec())
+        MythDB::DBError("StorageGroup::Delete", query);
+}
+
+void StorageGroup::Save(void)
+{
+    ConfigurationWizard::Save();
+
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    // apply deletes
+
+    for (QStringList::iterator oldDir = m_originaldirs.begin();
+         oldDir != m_originaldirs.end(); ++oldDir)
+    {
+        if (!m_dirlist.contains(*oldDir))
+        {
+            query.prepare("DELETE FROM storagegroup "
+                          "WHERE groupname = :NAME "
+                          "AND dirname = :DIRNAME "
+                          "AND hostname = :HOSTNAME;");
+            query.bindValue(":NAME", m_groupname);
+            query.bindValue(":DIRNAME", (*oldDir) + "/");
+            query.bindValue(":HOSTNAME", m_hostname);
+            if (!query.exec())
+                MythDB::DBError("StorageGroup::save", query);
+        }
+    }
+
+    // apply inserts
+
+    for (QStringList::iterator newDir = m_dirlist.begin();
+         newDir != m_dirlist.end(); ++newDir)
+    {
+        if (!m_originaldirs.contains(*newDir))
+        {
+            query.prepare("INSERT INTO storagegroup (groupname, hostname, dirname) "
+                          "VALUES (:NAME, :HOSTNAME, :DIRNAME);");
+            query.bindValue(":NAME", m_groupname);
+            query.bindValue(":DIRNAME", (*newDir) + "/");
+            query.bindValue(":HOSTNAME", m_hostname);
+            if (!query.exec())
+                MythDB::DBError("StorageGroup::save", query);
+        }
+    }
+}
+
+bool StorageGroup::hasChanged(void)
+{
+    if (m_originaldirs.size() != m_dirlist.size())
+        return true;
+
+    for (QStringList::iterator oldDir = m_originaldirs.begin();
+         oldDir != m_originaldirs.end(); ++oldDir)
+    {
+        if (!m_dirlist.contains(*oldDir))
+            return true;
+    }
+
+    return false;
+}
+
+/** \brief Adds a directory to a storage group.
+ *  \param directory    directory to add.
+ *  \param group        group to add to. defaults to "Default".
+ *  \param hostname     host to associate with mapping.
+ *                      Defaults to the local host name.
+ *  \return Name of directory after adjustments or an empty string
+ *          if the directory could not be added.
+ */
+QString StorageGroup::AddDir(const QString directory)
+{
+    QString name = directory;
+
+    if (name.right(1) == "/")
+        name.remove(name.length()-1, 1);
+
+    if (!m_dirlist.contains(name))
+        m_dirlist.append(name);
+
+    return name;
+}
+
+/** \brief Deletes a directory from a storage group.
+ *  \param directory    directory to remove.
+ *  \param group        group to remove from. defaults to "Default".
+ *  \param hostname     host to remove for. Defaults to the
+ *                      local host name.
+ *  \return Empty string if delete was successful otherwise directory
+ *          name after adjustments.
+ */
+QString StorageGroup::DelDir(const QString directory)
+{
+    QString name = directory;
+
+    if (name.right(1) == "/")
+        name.remove(name.length()-1, 1);
+
+    int pos = m_dirlist.indexOf(name);
+    if (pos != -1)
+    {
+        m_dirlist.removeAt(pos);
+        name.truncate(0);
+    }
+
+    return name;
+}
+
+/** \brief Return names of all files found in the storage group's directories.
+ *  \param descendIntoSubDirs   traverse all subdirectories.
+ *  \param followSymbolicLinks  include symbolically linked files in the list.
+ *                              When descendIntoSubDirs is true then links to
+ *                              folders will be traversed as well.
+ *  \return A map of the storage group's directories paired with a QStringList
+ *          of relative filenames found in that directory.
+ */
+StorageGroup::FilesList StorageGroup::GetAllFiles(bool descendIntoSubDirs,
+                                                  bool followSymbolicLinks)
+{
+    FilesList allFiles;
+    for (QStringList::iterator dir = m_dirlist.begin(); dir != m_dirlist.end();
+         dir++)
+    {
+        QStringList files = GetAllFiles(*dir, descendIntoSubDirs, followSymbolicLinks);
+        if (files.size() > 0)
+        {
+            allFiles[*dir] = files;
+        }
+    }
+
+    return allFiles;
+}
+
+/** \brief Return name of all files stored within a directory.
+ *  \param directory            directory to search.
+ *  \param descendIntoSubDirs   traverse all subdirectories.
+ *  \param followSymbolicLinks  include symbolically linked files in the list.
+ *                              When descendIntoSubDirs is true then links to
+ *                              folders will be traversed as well.
+ *  \return A QStringList of relative filenames found in the directory.
+ */
+QStringList StorageGroup::GetAllFiles(const QString directory,
+                                      bool descendIntoSubDirs,
+                                      bool followSymbolicLinks)
+{
+    QStringList allFiles;
+    StorageGroup::GetAllFilesImpl(directory,
+                                  allFiles,
+                                  descendIntoSubDirs,
+                                  followSymbolicLinks,
+                                  "");
+    return allFiles;
+}
+
+void StorageGroup::GetAllFilesImpl(const QString directory,
+                                   QStringList& fileList,
+                                   bool descendIntoSubDirs,
+                                   bool followSymbolicLinks,
+                                   const QString prefix)
+{
+    QDir dir(directory + prefix);
+    if (!dir.isReadable())
+        return;
+
+    dir.setSorting(QDir::Unsorted);
+
+    QFileInfoList files = dir.entryInfoList();
+
+    for (int i = 0; i < files.size(); i++)
+    {
+        QFileInfo fi = files.at(i);
+
+        QString fileName = fi.fileName();
+
+        if (fileName == "." || fileName == ".." ||
+            (!descendIntoSubDirs && fi.isDir()) ||
+            (!followSymbolicLinks && fi.isSymLink()))
+        {
+            continue;
+        }
+
+        if (fi.isDir())
+        {
+            StorageGroup::GetAllFilesImpl(directory,
+                                          fileList,
+                                          descendIntoSubDirs,
+                                          followSymbolicLinks,
+                                          prefix + "/" + fileName);
+        }
+        else
+        {
+            fileList.append(prefix.mid(1) + fileName);
+        }
+    }
+}
+
 QStringList StorageGroup::GetFileList(QString Path)
 {
     QStringList files;
@@ -342,6 +559,85 @@
     return nextDir;
 }
 
+/** \brief return list of StorageGroup names.
+ *  \param hostname restrict list to this host, blank will search all
+ *                  hosts' directories.
+ */
+QStringList StorageGroup::GetStorageGroupNames(const QString hostname)
+{
+    QStringList groups;
+
+    MSqlQuery query(MSqlQuery::InitCon());
+    if (hostname.isEmpty())
+    {
+        query.prepare("SELECT DISTINCT groupname "
+                      "FROM storagegroup "
+                      "ORDER BY groupname;");
+    }
+    else
+    {
+        query.prepare("SELECT DISTINCT groupname "
+                      "FROM storagegroup "
+                      "WHERE hostname = :HOSTNAME "
+                      "ORDER BY groupname;");
+        query.bindValue(":HOSTNAME", hostname);
+    }
+
+    if (query.exec() && query.isActive())
+    {
+        while (query.next())
+            groups += query.value(0).toString();
+    }
+    else
+        MythDB::DBError("StorageGroup::GetStorageGroupNames getting group names",
+                             query);
+
+    return groups;
+}
+
+/** \brief find name of the StorageGroup containing a given directory.
+ *  \param directory directory to lookup.
+ *  \param hostname restrict search to this host. Blank, the default,
+ *                  will search all hosts' directories.
+ *
+ *  N.B. when two different StorageGroups contain the same directory the
+ *  return value will be unpredictable.
+ */
+QString StorageGroup::FindStorageGroupForDir(const QString directory, const QString hostname)
+{
+    MSqlQuery query(MSqlQuery::InitCon());
+
+    QString searchDir(directory);
+    if (searchDir.right(1) != "/")
+        searchDir.append("/");
+
+    if (hostname.isEmpty())
+    {
+        query.prepare("SELECT groupname "
+                      "FROM storagegroup "
+                      "WHERE dirname = :DIR");
+    }
+    else
+    {
+        query.prepare("SELECT groupname "
+                      "FROM storagegroup "
+                      "WHERE hostname = :HOSTNAME "
+                      "  AND dirname = :DIR");
+        query.bindValue(":HOSTNAME", hostname);
+    }
+    query.bindValue(":DIR", searchDir);
+
+    if (!query.exec() || !query.isActive())
+    {
+        MythDB::DBError("StorageGroup::FindStorageGroupForDir()", query);
+        return QString();
+    }
+
+    return query.next()
+        ? query.value(0).toString()
+        : QString();
+}
+
 void StorageGroup::CheckAllStorageGroupDirs(void)
 {
     QString m_groupname;
@@ -505,7 +801,7 @@
 /****************************************************************************/
 
 StorageGroupEditor::StorageGroupEditor(QString group) :
-    m_group(group), listbox(new ListBoxSetting(this)), lastValue("")
+    listbox(new ListBoxSetting(this)), lastValue("")
 {
     QString dispGroup = group;
 
@@ -514,6 +810,8 @@
     else if (StorageGroup::kSpecialGroups.contains(group))
         dispGroup = QObject::tr(group.toLatin1().constData());
 
+    m_group = new StorageGroup(group, gContext->GetHostName(), false);
+
     if (gContext->GetSetting("MasterServerIP","master") ==
             gContext->GetSetting("BackendServerIP","me"))
     {
@@ -528,6 +826,12 @@
     addChild(listbox);
 }
 
+StorageGroupEditor::~StorageGroupEditor()
+{
+    if (m_group)
+        delete m_group;
+}
+
 void StorageGroupEditor::open(QString name) 
 {
     lastValue = name;
@@ -546,19 +850,7 @@
         if (name.isEmpty())
             return;
 
-        if (name.right(1) != "/")
-            name.append("/");
-
-        MSqlQuery query(MSqlQuery::InitCon());
-        query.prepare("INSERT INTO storagegroup (groupname, hostname, dirname) "
-                      "VALUES (:NAME, :HOSTNAME, :DIRNAME);");
-        query.bindValue(":NAME", m_group);
-        query.bindValue(":DIRNAME", name);
-        query.bindValue(":HOSTNAME", gContext->GetHostName());
-        if (!query.exec())
-            MythDB::DBError("StorageGroupEditor::open", query);
-        else
-            lastValue = name;
+        lastValue = m_group->AddDir(name);
     } else {
         SGPopupResult result = StorageGroupPopup::showPopup(
             gContext->GetMainWindow(), 
@@ -568,30 +860,9 @@
         if (result == SGPopup_CANCEL)
             return;
 
-        if (name.right(1) != "/")
-            name.append("/");
-
-        MSqlQuery query(MSqlQuery::InitCon());
-
-        query.prepare("DELETE FROM storagegroup "
-                      "WHERE groupname = :NAME "
-                          "AND dirname = :DIRNAME "
-                          "AND hostname = :HOSTNAME;");
-        query.bindValue(":NAME", m_group);
-        query.bindValue(":DIRNAME", lastValue);
-        query.bindValue(":HOSTNAME", gContext->GetHostName());
-        if (!query.exec())
-            MythDB::DBError("StorageGroupEditor::open", query);
-
-        query.prepare("INSERT INTO storagegroup (groupname, hostname, dirname) "
-                      "VALUES (:NAME, :HOSTNAME, :DIRNAME);");
-        query.bindValue(":NAME", m_group);
-        query.bindValue(":DIRNAME", name);
-        query.bindValue(":HOSTNAME", gContext->GetHostName());
-        if (!query.exec())
-            MythDB::DBError("StorageGroupEditor::open", query);
-        else
-            lastValue = name;
+        lastValue = m_group->DelDir(lastValue);
+        if (lastValue.isEmpty())
+            lastValue = m_group->AddDir(name);
     }
 };
 
@@ -612,21 +883,13 @@
 
     if (kDialogCodeButton0 == value)
     {
-        MSqlQuery query(MSqlQuery::InitCon());
-        query.prepare("DELETE FROM storagegroup "
-                      "WHERE groupname = :NAME "
-                          "AND dirname = :DIRNAME "
-                          "AND hostname = :HOSTNAME;");
-        query.bindValue(":NAME", m_group);
-        query.bindValue(":DIRNAME", name);
-        query.bindValue(":HOSTNAME", gContext->GetHostName());
-        if (!query.exec())
-            MythDB::DBError("StorageGroupEditor::doDelete", query);
-
-        int lastIndex = listbox->getValueIndex(name);
-        lastValue = "";
-        Load();
-        listbox->setValue(lastIndex);
+        if (m_group->DelDir(name).isEmpty())
+        {
+            int lastIndex = listbox->getValueIndex(name);
+            lastValue = "";
+            Load();
+            listbox->setValue(lastIndex);
+        }
     }
 
     listbox->setFocus();
@@ -636,26 +899,17 @@
 {
     listbox->clearSelections();
 
-    MSqlQuery query(MSqlQuery::InitCon());
-    query.prepare("SELECT dirname, id FROM storagegroup "
-                  "WHERE groupname = :NAME AND hostname = :HOSTNAME "
-                  "ORDER BY id;");
-    query.bindValue(":NAME", m_group);
-    query.bindValue(":HOSTNAME", gContext->GetHostName());
-    if (!query.exec() || !query.isActive())
-        MythDB::DBError("StorageGroupEditor::doDelete", query);
-    else
+    QStringList dirs = m_group->GetDirList();
+
+    bool first = true;
+    for (QStringList::iterator dir = dirs.begin(); dir != dirs.end(); ++dir)
     {
-        bool first = true;
-        while (query.next())
+        if (first)
         {
-            if (first)
-            {
-                lastValue = query.value(0).toString();
-                first = false;
-            }
-            listbox->addSelection(query.value(0).toString());
+            lastValue = *dir;
+            first = false;
         }
+        listbox->addSelection(*dir);
     }
 
     listbox->addSelection(tr("(Add New Directory)"),
@@ -664,9 +918,26 @@
     listbox->setValue(lastValue);
 }
 
+void StorageGroupEditor::Save(void)
+{
+    if (m_group)
+        m_group->Save();
+}
+
+void StorageGroupEditor::Delete(void)
+{
+    if (m_group)
+        m_group->Delete();
+}
+
+bool StorageGroupEditor::hasChanged(void)
+{
+    return m_group && m_group->hasChanged();
+}
+
 DialogCode StorageGroupEditor::exec(void)
 {
-    while (ConfigurationDialog::exec() == kDialogCodeAccepted)
+    while (ConfigurationDialog::exec(false) == kDialogCodeAccepted)
         open(listbox->getValue());
 
     return kDialogCodeRejected;
@@ -697,6 +968,19 @@
     addChild(listbox);
 }
 
+StorageGroupListEditor::~StorageGroupListEditor(void)
+{
+    for (StorageGroupEditors::Iterator sge = m_deleted.begin(); sge != m_deleted.end(); ++sge)
+    {
+        delete *sge;
+    }
+
+    for (StorageGroupEditors::Iterator sge = m_edited.begin(); sge != m_edited.end(); ++sge)
+    {
+        delete *sge;
+    }
+}
+
 void StorageGroupListEditor::open(QString name) 
 {
     lastValue = name;
@@ -722,8 +1006,21 @@
 
     if (!name.isEmpty())
     {
-        StorageGroupEditor sgEditor(name);
-        sgEditor.exec();
+        StorageGroupEditor *sgEditor;
+
+        StorageGroupEditors::iterator
+            editor = m_edited.find(name);
+
+        if (editor == m_edited.end())
+        {
+            sgEditor = new StorageGroupEditor(name);
+            m_edited[name] = sgEditor;
+        }
+        else
+            sgEditor = *editor;
+
+        sgEditor->exec();
+        Load();
     }
 };
 
@@ -754,14 +1051,19 @@
 
     if (kDialogCodeButton0 == value)
     {
-        MSqlQuery query(MSqlQuery::InitCon());
-        query.prepare("DELETE FROM storagegroup "
-                      "WHERE groupname = :NAME AND hostname = :HOSTNAME;");
-        query.bindValue(":NAME", name);
-        query.bindValue(":HOSTNAME", gContext->GetHostName());
-        if (!query.exec())
-            MythDB::DBError("StorageGroupListEditor::doDelete", query);
+        StorageGroupEditors::Iterator
+            edited = m_edited.find(name);
 
+        // N.B. this will leak if user deletes/adds/deletes
+        // the same name. Too bad.
+        if (edited == m_edited.end())
+            m_deleted[name] = new StorageGroupEditor(name);
+        else
+        {
+            m_deleted[name] = *edited;
+            m_edited.erase(edited);
+        }
+
         int lastIndex = listbox->getValueIndex(name);
         lastValue = "";
         Load();
@@ -780,33 +1082,34 @@
     bool isMaster = (gContext->GetSetting("MasterServerIP","master") ==
                      gContext->GetSetting("BackendServerIP","me"));
 
-    MSqlQuery query(MSqlQuery::InitCon());
-    query.prepare("SELECT distinct groupname "
-                  "FROM storagegroup "
-                  "WHERE hostname = :HOSTNAME "
-                  "ORDER BY groupname;");
-    query.bindValue(":HOSTNAME", gContext->GetHostName());
-    if (!query.exec())
-        MythDB::DBError("StorageGroup::Load getting local group names",
-                             query);
-    else
+    QStringList localGroups =
+        StorageGroup::GetStorageGroupNames(gContext->GetHostName());
+
+    for (QStringList::Iterator sg = localGroups.begin(); sg != localGroups.end(); ++sg)
     {
-        while (query.next())
-            names << query.value(0).toString();
+        bool isDeleted =
+            m_deleted.contains(*sg) && ! m_edited.contains(*sg);
+
+        if (!isDeleted)
+            names << *sg;
     }
 
-    query.prepare("SELECT distinct groupname "
-                  "FROM storagegroup "
-                  "ORDER BY groupname;");
-    if (!query.exec())
-        MythDB::DBError("StorageGroup::Load getting all group names",
-                             query);
-    else
+    // groups added in this editor session won't appear in
+    // localGroups which pulls from the database
+    for (StorageGroupEditors::Iterator sge = m_edited.begin(); sge != m_edited.end(); ++sge)
     {
-        while (query.next())
-            masterNames << query.value(0).toString();
+        if (!names.contains(sge.key()))
+            names << sge.key();
     }
 
+    QStringList allGroups =
+        StorageGroup::GetStorageGroupNames();
+
+    for (QStringList::Iterator sg = allGroups.begin(); sg != allGroups.end(); ++sg)
+    {
+        masterNames << *sg;
+    }
+
     listbox->clearSelections();
 
     if (isMaster || names.contains("Default"))
@@ -880,9 +1183,34 @@
     listbox->setValue(lastValue);
 }
 
+void StorageGroupListEditor::Save(void)
+{
+    for (StorageGroupEditors::Iterator sge = m_deleted.begin(); sge != m_deleted.end(); ++sge)
+    {
+        (*sge)->Delete();
+    }
+
+    for (StorageGroupEditors::Iterator sge = m_edited.begin(); sge != m_edited.end(); ++sge)
+    {
+        (*sge)->Save();
+    }
+}
+
+bool StorageGroupListEditor::hasChanged(void)
+{
+    if (!m_deleted.empty())
+        return true;
+
+    for (StorageGroupEditors::Iterator sge = m_edited.begin(); sge != m_edited.end(); ++sge)
+        if ((*sge)->hasChanged())
+            return true;
+
+    return false;
+}
+
 DialogCode StorageGroupListEditor::exec(void)
 {
-    while (ConfigurationDialog::exec() == kDialogCodeAccepted)
+    while (ConfigurationDialog::exec(false) == kDialogCodeAccepted)
         open(listbox->getValue());
 
     return kDialogCodeRejected;
Index: programs/mythtv-setup/main.cpp
===================================================================
--- programs/mythtv-setup/main.cpp	(revision 19997)
+++ programs/mythtv-setup/main.cpp	(working copy)
@@ -66,6 +66,8 @@
     {
         StorageGroupListEditor sge;
         sge.exec();
+        if (sge.hasChanged())
+            sge.Save();
     }
     else if (sel == "exiting_app")
     {
