Index: mythplugins/mythweb/modules/_shared/lang/English.lang
===================================================================
--- mythplugins/mythweb/modules/_shared/lang/English.lang	(revision 14120)
+++ mythplugins/mythweb/modules/_shared/lang/English.lang	(working copy)
@@ -171,6 +171,7 @@
 "Delete and allow rerecord: $1"
 "Delete and rerecord $1"
 "DELETE this Saved Playlist"
+"Undelete"
 "description"
 "Description"
 "Destination"
Index: mythplugins/mythweb/modules/tv/tmpl/default/recorded.php
===================================================================
--- mythplugins/mythweb/modules/tv/tmpl/default/recorded.php	(revision 14120)
+++ mythplugins/mythweb/modules/tv/tmpl/default/recorded.php	(working copy)
@@ -341,6 +341,13 @@
         <a onclick="javascript:confirm_delete(<?php echo $row ?>, true)"
             title="<?php echo html_entities(t('Delete and allow rerecord: $1', preg_replace('/: $/', '', $show->title.': '.$show->subtitle))) ?>"
             ><?php echo t('Delete + Rerecord') ?></a>
+
+<?php       if ($show->recgroup == 'Deleted') {
+                echo '<a href="', root, 'tv/recorded?undelete=yes&chanid=', $show->chanid,
+                    '&starttime=', $show->starttime, '" ' ?>
+                title="<?php echo html_entities(t('Undelete: $1', preg_replace('/: $/', '', $show->title.': '.$show->subtitle))) ?>"
+                <?php echo '>' , t('Undelete') ?></a>
+<?php       } ?>
 <?php   } ?>
         </td>
 </tr><tr id="statusrow_<?php echo $row ?>" class="recorded">
Index: mythplugins/mythweb/modules/tv/recorded.php
===================================================================
--- mythplugins/mythweb/modules/tv/recorded.php	(revision 14120)
+++ mythplugins/mythweb/modules/tv/recorded.php	(working copy)
@@ -19,11 +19,15 @@
 // Load the sorting routines
     require_once 'includes/sorting.php';
 
-// Delete a program?
+// Delete or undelete a program?
     isset($_GET['forget_old']) or $_GET['forget_old'] = $_POST['forget_old'];
     isset($_GET['delete'])     or $_GET['delete']     = $_POST['delete'];
     isset($_GET['file'])       or $_GET['file']       = $_POST['file'];
-    if ($_GET['delete']) {
+    if ($_GET['delete'] || $_GET['undelete']) {
+        if ($_GET['delete'])
+          $backendstr = 'DELETE_RECORDING';
+        else 
+          $backendstr = 'UNDELETE_RECORDING';
     // Keep a previous-row counter to return to after deleting
         $prev_row = -2;
     // We need to scan through the available recordings to get at the additional information required by the DELETE_RECORDING query
@@ -35,7 +39,7 @@
             if ($row[4] != $_GET['chanid'] || $row[26] != $_GET['starttime'])
                 continue;
         // Delete the recording
-            backend_command(array('FORCE_DELETE_RECORDING', implode(backend_sep, $row), '0'));
+            backend_command(array($backendstr, implode(backend_sep, $row), '0'));
         // Forget all knowledge of old recordings?
             if (isset($_GET['forget_old'])) {
                 backend_command(array('FORGET_RECORDING', implode(backend_sep, $row), '0'));
@@ -105,8 +109,8 @@
         // keep track of their names and how many episodes we have recorded
             $Total_Programs++;
             $Groups[$record[30]]++;
-        // Hide LiveTV recordings from the title list
-            if (($_GET['recgroup'] && $_GET['recgroup'] == $record[30]) || (!$_GET['recgroup'] && $record[30] != 'LiveTV'))
+        // Hide LiveTV  and Deleted recordings from the title list
+            if (($_GET['recgroup'] && $_GET['recgroup'] == $record[30]) || (!$_GET['recgroup'] && $record[30] != 'LiveTV' && $record[30] != 'Deleted'))
                 $Program_Titles[$record[0]]++;
         // Skip files with no chanid
             if (!$record[4])
@@ -116,8 +120,8 @@
                 continue;
             if ($_GET['recgroup'] && $_GET['recgroup'] != $record[30])
                 continue;
-        // Hide livetv recordings from the default view
-            if (empty($_GET['recgroup']) && $record[30] == 'LiveTV')
+        // Hide LiveTV recordings from the default view
+            if (empty($_GET['recgroup']) && ($record[30] == 'LiveTV' || $record[30] == 'Deleted'))
                 continue;
         // Make sure that everything we're dealing with is an array
             if (!is_array($Programs[$record[0]]))
Index: mythtv/libs/libmythtv/remoteutil.h
===================================================================
--- mythtv/libs/libmythtv/remoteutil.h	(revision 14120)
+++ mythtv/libs/libmythtv/remoteutil.h	(working copy)
@@ -39,6 +39,8 @@
 bool RemoteDeleteRecording(ProgramInfo *pginfo, bool forgetHistory,
                            bool forceMetadataDelete = false);
 MPUBLIC
+bool RemoteUndeleteRecording(ProgramInfo *pginfo);
+MPUBLIC
 void RemoteGetAllScheduledRecordings(vector<ProgramInfo *> &scheduledlist);
 MPUBLIC
 void RemoteGetAllExpiringRecordings(vector<ProgramInfo *> &expiringlist);
Index: mythtv/libs/libmythtv/remoteutil.cpp
===================================================================
--- mythtv/libs/libmythtv/remoteutil.cpp	(revision 14120)
+++ mythtv/libs/libmythtv/remoteutil.cpp	(working copy)
@@ -164,6 +164,27 @@
     return result;
 }
 
+bool RemoteUndeleteRecording(ProgramInfo *pginfo)
+{
+    bool result = false;
+
+    bool undelete_possible = gContext->GetNumSetting("AutoExpireInsteadOfDelete");
+    if (!undelete_possible)
+        return result;
+
+    QStringList strlist;
+
+    strlist = QString("UNDELETE_RECORDING");
+    pginfo->ToStringList(strlist);
+
+    gContext->SendReceiveStringList(strlist);
+
+    if (strlist[0].toInt() == 0)
+        result = true;
+
+    return result;
+}
+
 void RemoteGetAllScheduledRecordings(vector<ProgramInfo *> &scheduledlist)
 {
     QStringList strList = QString("QUERY_GETALLSCHEDULED");
Index: mythtv/programs/mythfrontend/playbackbox.cpp
===================================================================
--- mythtv/programs/mythfrontend/playbackbox.cpp	(revision 14120)
+++ mythtv/programs/mythfrontend/playbackbox.cpp	(working copy)
@@ -1664,7 +1664,8 @@
             p = *i;
             if ((((p->recgroup == recGroup) ||
                   ((recGroup == "All Programs") &&
-                   (p->recgroup != "LiveTV" || LiveTVInAllPrograms))) &&
+                   ((p->recgroup != "LiveTV" && p->recgroup != "Deleted")
+                    || LiveTVInAllPrograms))) &&
                  (recGroupPassword == curGroupPassword)) ||
                 ((recGroupType[recGroup] == "category") &&
                  ((p->category == recGroup ) ||
@@ -3159,6 +3160,10 @@
         popup->addButton(tr("Stop Recording"), this, SLOT(askStop()));
     }
 
+    if (program->recgroup == "Deleted")
+        popup->addButton(tr("Undelete"), this,
+                        SLOT(doUndelete()));
+
     if (curitem->programflags & FL_WATCHED)
         popup->addButton(tr("Mark as Unwatched"), this,
                                     SLOT(setUnwatched()));
@@ -3517,6 +3522,18 @@
     playList.clear();
 }
 
+void PlaybackBox::doUndelete(void)
+{
+    if (!expectingPopup)
+    {
+        previewSuspend = false;
+        return;
+    }
+
+    cancelPopup();
+    RemoteUndeleteRecording(curitem);
+}
+
 void PlaybackBox::doDelete(void)
 {
     if (!expectingPopup)
Index: mythtv/programs/mythfrontend/globalsettings.cpp
===================================================================
--- mythtv/programs/mythfrontend/globalsettings.cpp	(revision 14120)
+++ mythtv/programs/mythfrontend/globalsettings.cpp	(working copy)
@@ -576,6 +576,17 @@
     return bs;
 };
 
+static GlobalCheckBox *AutoExpireInsteadOfDelete()
+{
+    GlobalCheckBox *cb = new GlobalCheckBox("AutoExpireInsteadOfDelete");
+    cb->setLabel(QObject::tr("Auto Expire Instead of Delete Recording"));
+    cb->setValue(false);
+    cb->setHelpText(QObject::tr("Instead of deleting a recording, "
+                                "move recording to the 'Deleted' recgroup "
+                                "and turn on autoexpire."));
+    return cb;
+}
+
 static GlobalComboBox *AutoExpireMethod()
 {
     GlobalComboBox *bc = new GlobalComboBox("AutoExpireMethod");
@@ -3736,6 +3747,7 @@
     autoexp->addChild(AutoExpireLiveTVMaxAge());
     autoexp->addChild(RerecordWatched());
     autoexp->addChild(AutoExpireExtraSpace());
+    autoexp->addChild(AutoExpireInsteadOfDelete());
     addChild(autoexp);
 
     VerticalConfigurationGroup* jobs = new VerticalConfigurationGroup(false);
Index: mythtv/programs/mythfrontend/playbackbox.h
===================================================================
--- mythtv/programs/mythfrontend/playbackbox.h	(revision 14120)
+++ mythtv/programs/mythfrontend/playbackbox.h	(working copy)
@@ -160,6 +160,7 @@
     void doEditScheduled();
 
     void askDelete();
+    void doUndelete();
     void doDelete();
     void doDeleteForgetHistory();
     void doForceDelete();
Index: mythtv/programs/mythbackend/mainserver.h
===================================================================
--- mythtv/programs/mythbackend/mainserver.h	(revision 14120)
+++ mythtv/programs/mythbackend/mainserver.h	(working copy)
@@ -80,6 +80,8 @@
                                bool forceMetadataDelete);
     void DoHandleDeleteRecording(ProgramInfo *pginfo, PlaybackSock *pbs,
                                  bool forceMetadataDelete);
+    void HandleUndeleteRecording(QStringList &slist, PlaybackSock *pbs);
+    void DoHandleUndeleteRecording(ProgramInfo *pginfo, PlaybackSock *pbs);
     void HandleForgetRecording(QStringList &slist, PlaybackSock *pbs);
     void HandleRescheduleRecordings(int recordid, PlaybackSock *pbs);
     void HandleQueryFreeSpace(PlaybackSock *pbs, bool allBackends);
Index: mythtv/programs/mythbackend/mainserver.cpp
===================================================================
--- mythtv/programs/mythbackend/mainserver.cpp	(revision 14120)
+++ mythtv/programs/mythbackend/mainserver.cpp	(working copy)
@@ -396,6 +396,10 @@
     {
         HandleDeleteRecording(listline, pbs, true);
     }
+    else if (command == "UNDELETE_RECORDING")
+    {
+        HandleUndeleteRecording(listline, pbs);
+    }
     else if (command == "RESCHEDULE_RECORDINGS")
     {
         if (tokens.size() != 2)
@@ -1916,6 +1920,25 @@
     if (pbs)
         pbssock = pbs->getSocket();
 
+    bool justexpire = 
+            (gContext->GetNumSetting("AutoExpireInsteadOfDelete") &&
+            (pginfo->recgroup != "Deleted") && (pginfo->recgroup != "LiveTV"));
+
+    if (justexpire && !forceMetadataDelete)
+    {
+        pginfo->ApplyRecordRecGroupChange("Deleted");
+        pginfo->SetAutoExpire(1);
+        if (pginfo->recstatus == rsRecording)
+            DoHandleStopRecording(pginfo, pbs);
+        else
+            delete pginfo;
+        QStringList outputlist = QString::number(0);
+        SendResponse(pbssock, outputlist);
+        MythEvent me("RECORDING_LIST_CHANGE");
+        gContext->dispatch(me);
+        return;
+    }
+
     QString filename = pginfo->GetPlaybackURL();
 
     // If this recording was made by a another recorder, and that
@@ -2038,6 +2061,36 @@
     delete pginfo;
 }
 
+void MainServer::HandleUndeleteRecording(QStringList &slist, PlaybackSock *pbs)
+{
+    ProgramInfo *pginfo  = new ProgramInfo();
+    pginfo->FromStringList(slist, 1);
+
+    DoHandleUndeleteRecording(pginfo, pbs);
+}
+
+void MainServer::DoHandleUndeleteRecording(ProgramInfo *pginfo, PlaybackSock *pbs)
+{
+    bool ret = -1;
+    bool undelete_possible = gContext->GetNumSetting("AutoExpireInsteadOfDelete");
+    MythSocket *pbssock = NULL;
+    if (pbs)
+        pbssock = pbs->getSocket();
+
+    if (undelete_possible)
+    {
+        pginfo->ApplyRecordRecGroupChange("Default");
+        pginfo->UpdateLastDelete(false);
+        delete pginfo;
+        ret = 0;
+        MythEvent me("RECORDING_LIST_CHANGE");
+        gContext->dispatch(me);
+    }
+
+    QStringList outputlist = QString::number(ret);
+    SendResponse(pbssock, outputlist);
+}
+
 void MainServer::HandleRescheduleRecordings(int recordid, PlaybackSock *pbs)
 {
     QStringList result;
