Index: mythplugins/mythweb/modules/tv/tmpl/default/recorded.php
===================================================================
--- mythplugins/mythweb/modules/tv/tmpl/default/recorded.php	(revision 14335)
+++ mythplugins/mythweb/modules/tv/tmpl/default/recorded.php	(working copy)
@@ -337,10 +337,22 @@
         if ($show->can_delete) {
         ?><a onclick="javascript:confirm_delete(<?php echo $row ?>, false)"
             title="<?php echo html_entities(t('Delete $1', preg_replace('/: $/', '', $show->title.': '.$show->subtitle))) ?>"
-            ><?php echo t('Delete') ?></a>
+            ><?php if (get_backend_setting('AutoExpireInsteadOfDelete') > 0 &&
+                       $show->recgroup == 'Deleted')
+                        echo t('Delete Forever');
+                    else
+                        echo t('Delete');
+              ?></a>
         <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 14335)
+++ 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/programs/mythfrontend/playbackbox.cpp
===================================================================
--- mythtv/programs/mythfrontend/playbackbox.cpp	(revision 14335)
+++ mythtv/programs/mythfrontend/playbackbox.cpp	(working copy)
@@ -1664,6 +1664,7 @@
             p = *i;
             if ((((p->recgroup == recGroup) ||
                   ((recGroup == "All Programs") &&
+                   (p->recgroup != "Deleted") &&
                    (p->recgroup != "LiveTV" || LiveTVInAllPrograms))) &&
                  (recGroupPassword == curGroupPassword)) ||
                 ((recGroupType[recGroup] == "category") &&
@@ -2304,7 +2305,12 @@
     if (!curitem)
         return;
 
-    if ((curitem->availableStatus != asPendingDelete) &&
+    bool undelete_possible =
+            gContext->GetNumSetting("AutoExpireInsteadOfDelete", 0);
+
+    if (curitem->recgroup != "Deleted" && undelete_possible)
+        doRemove(curitem, false, false);
+    else if ((curitem->availableStatus != asPendingDelete) &&
         (REC_CAN_BE_DELETED(curitem)))
         remove(curitem);
     else
@@ -3174,7 +3180,20 @@
     popup->addButton(tr("Job Options"), this, SLOT(showJobPopup()));
 
     if (!(m_player && m_player->IsSameProgram(curitem)))
-        popup->addButton(tr("Delete"), this, SLOT(askDelete()));
+    {
+        if (curitem->recgroup == "Deleted")
+        {
+            popup->addButton(tr("Undelete"), this,
+                        SLOT(doUndelete()));
+            popup->addButton(tr("Delete Forever"), this,
+                        SLOT(doDelete()));
+        }
+        else
+        {
+            popup->addButton(tr("Delete"), this,
+                        SLOT(askDelete()));
+        }
+    }
 
     popup->ShowPopup(this, SLOT(doCancel()));
 
@@ -3522,6 +3541,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 14335)
+++ mythtv/programs/mythfrontend/globalsettings.cpp	(working copy)
@@ -577,6 +577,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");
@@ -3823,6 +3834,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 14335)
+++ 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 14335)
+++ 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 14335)
+++ 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(kDeletedAutoExpire);
+        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,38 @@
     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", 0);
+    MythSocket *pbssock = NULL;
+    if (pbs)
+        pbssock = pbs->getSocket();
+
+    if (undelete_possible)
+    {
+        pginfo->ApplyRecordRecGroupChange("Default");
+        pginfo->UpdateLastDelete(false);
+        pginfo->SetAutoExpire(kDisableAutoExpire);
+        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;
