{
dvr_entry_sched_state_t state = entry->de_sched_state;
return state == DVR_COMPLETED && !entry->de_last_error &&
- (dvr_get_filesize(entry, 0) != -1 || entry->de_file_removed) &&
+ dvr_get_filesize(entry, 0) != -1 && !entry->de_file_removed &&
entry->de_data_errors < DVR_MAX_DATA_ERRORS;
}
+static int is_dvr_entry_removed(dvr_entry_t *entry)
+{
+ dvr_entry_sched_state_t state = entry->de_sched_state;
+ return ((state == DVR_COMPLETED || state == DVR_MISSED_TIME) && entry->de_file_removed);
+}
+
static int is_dvr_entry_upcoming(dvr_entry_t *entry)
{
dvr_entry_sched_state_t state = entry->de_sched_state;
return 0;
if (is_dvr_entry_upcoming(entry))
return 0;
+ if (is_dvr_entry_removed(entry))
+ return 0;
return 1;
}
idnode_set_add(ins, (idnode_t*)de, &conf->filter, perm->aa_lang_ui);
}
+static void
+api_dvr_entry_grid_removed
+ ( access_t *perm, idnode_set_t *ins, api_idnode_grid_conf_t *conf, htsmsg_t *args )
+{
+ dvr_entry_t *de;
+
+ LIST_FOREACH(de, &dvrentries, de_global_link)
+ if (is_dvr_entry_removed(de))
+ idnode_set_add(ins, (idnode_t*)de, &conf->filter, perm->aa_lang_ui);
+}
+
static int
api_dvr_entry_create
( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
return api_idnode_handler(perm, args, resp, api_dvr_cancel, "cancel", 0);
}
+static void
+api_dvr_remove(access_t *perm, idnode_t *self)
+{
+ dvr_entry_t *de = (dvr_entry_t *)self;
+ if (de->de_sched_state != DVR_SCHEDULED && de->de_sched_state != DVR_NOSTATE)
+ dvr_entry_cancel_remove(de, 0);
+}
+
+static int
+api_dvr_entry_remove
+ ( access_t *perm, void *opaque, const char *op, htsmsg_t *args, htsmsg_t **resp )
+{
+ return api_idnode_handler(perm, args, resp, api_dvr_remove, "remove", 0);
+}
+
static void
api_dvr_move_finished(access_t *perm, idnode_t *self)
{
{ "dvr/entry/grid_upcoming", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_upcoming },
{ "dvr/entry/grid_finished", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_finished },
{ "dvr/entry/grid_failed", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_failed },
+ { "dvr/entry/grid_removed", ACCESS_RECORDER, api_idnode_grid, api_dvr_entry_grid_removed },
{ "dvr/entry/create", ACCESS_RECORDER, api_dvr_entry_create, NULL },
{ "dvr/entry/create_by_event", ACCESS_RECORDER, api_dvr_entry_create_by_event, NULL },
{ "dvr/entry/rerecord/toggle", ACCESS_RECORDER, api_dvr_entry_rerecord_toggle, NULL },
{ "dvr/entry/rerecord/deny", ACCESS_RECORDER, api_dvr_entry_rerecord_deny, NULL },
{ "dvr/entry/rerecord/allow", ACCESS_RECORDER, api_dvr_entry_rerecord_allow, NULL },
- { "dvr/entry/stop", ACCESS_RECORDER, api_dvr_entry_stop, NULL },
- { "dvr/entry/cancel", ACCESS_RECORDER, api_dvr_entry_cancel, NULL },
+ { "dvr/entry/stop", ACCESS_RECORDER, api_dvr_entry_stop, NULL }, /* Stop active recording gracefully */
+ { "dvr/entry/cancel", ACCESS_RECORDER, api_dvr_entry_cancel, NULL }, /* Cancel scheduled or active recording */
+ { "dvr/entry/remove", ACCESS_RECORDER, api_dvr_entry_remove, NULL }, /* Remove recorded files from storage */
{ "dvr/entry/filemoved", ACCESS_ADMIN, api_dvr_entry_file_moved, NULL },
{ "dvr/entry/move/finished", ACCESS_RECORDER, api_dvr_entry_move_finished, NULL },
{ "dvr/entry/move/failed", ACCESS_RECORDER, api_dvr_entry_move_failed, NULL },
int dvr_clone;
uint32_t dvr_rerecord_errors;
uint32_t dvr_retention_days;
- uint32_t dvr_retention_minimal;
uint32_t dvr_removal_days;
uint32_t dvr_autorec_max_count;
uint32_t dvr_autorec_max_sched_count;
int dvr_entry_delete(dvr_entry_t *de);
-void dvr_entry_cancel_delete(dvr_entry_t *de, int rerecord, int forcedestroy);
+void dvr_entry_cancel_delete(dvr_entry_t *de, int rerecord);
-void dvr_entry_trydestroy(dvr_entry_t *de);
+void dvr_entry_cancel_remove(dvr_entry_t *de, int rerecord);
int dvr_entry_file_moved(const char *src, const char *dst);
if (de_prev) {
tvhinfo(LS_DVR, "autorec %s removing recordings %s (allowed count %u total %u)",
dae->dae_name, idnode_uuid_as_str(&de_prev->de_id, ubuf), max_count, total);
- dvr_entry_cancel_delete(de_prev, 0, 0);
+ dvr_entry_cancel_remove(de_prev, 0);
}
}
}
cfg->dvr_enabled = 1;
cfg->dvr_config_name = strdup(name);
cfg->dvr_retention_days = DVR_RET_ONREMOVE;
- cfg->dvr_retention_minimal = DVR_RET_MIN_DISABLED;
cfg->dvr_removal_days = DVR_RET_REM_FOREVER;
cfg->dvr_clone = 1;
cfg->dvr_tag_files = 1;
return strtab2htsmsg_u32(tab, 1, lang);
}
-static htsmsg_t *
-dvr_config_class_retention_list_minimal ( void *o, const char *lang )
-{
- static const struct strtab_u32 tab[] = {
- { N_("Disabled"), DVR_RET_MIN_DISABLED },
- { N_("1 day"), DVR_RET_REM_1DAY },
- { N_("3 days"), DVR_RET_REM_3DAY },
- { N_("5 days"), DVR_RET_REM_5DAY },
- { N_("1 week"), DVR_RET_REM_1WEEK },
- { N_("2 weeks"), DVR_RET_REM_2WEEK },
- { N_("3 weeks"), DVR_RET_REM_3WEEK },
- { N_("1 month"), DVR_RET_REM_1MONTH },
- { N_("2 months"), DVR_RET_REM_2MONTH },
- { N_("3 months"), DVR_RET_REM_3MONTH },
- { N_("6 months"), DVR_RET_REM_6MONTH },
- { N_("1 year"), DVR_RET_REM_1YEAR },
- { N_("2 years"), DVR_RET_REM_2YEARS },
- { N_("3 years"), DVR_RET_REM_3YEARS },
- { N_("Forever"), DVR_RET_REM_FOREVER },
- };
- return strtab2htsmsg_u32(tab, 1, lang);
-}
-
static htsmsg_t *
dvr_config_class_extra_list(void *o, const char *lang)
{
.type = PT_U32,
.id = "retention-days",
.name = N_("DVR log retention period"),
- .desc = N_("Number of days to retain information about recordings. Once this period is exceeded, duplicate detection will not be possible for this recording."),
+ .desc = N_("Number of days to retain information about recordings. Once this period is exceeded, duplicate detection will not be possible anymore."),
.off = offsetof(dvr_config_t, dvr_retention_days),
.def.u32 = DVR_RET_ONREMOVE,
.list = dvr_config_class_retention_list,
.opts = PO_EXPERT | PO_DOC_NLIST,
.group = 1,
},
- {
- .type = PT_U32,
- .id = "retention-minimal",
- .name = N_("Minimal log retention period"),
- .desc = N_("Minimal number of days to retain information from recordings that where deleted manually. Once this period is exceeded, duplicate detection will not be possible for this recording."),
- .off = offsetof(dvr_config_t, dvr_retention_minimal),
- .def.u32 = DVR_RET_MIN_DISABLED,
- .list = dvr_config_class_retention_list_minimal,
- .opts = PO_EXPERT | PO_DOC_NLIST,
- .group = 1,
- },
{
.type = PT_U32,
.id = "removal-days",
dvr_entry_deferred_destroy(de); // also remove database entry
return;
}
- if (save)
+ if (save) {
idnode_changed(&de->de_id);
+ htsp_dvr_entry_update(de);
+ }
}
if (retention < DVR_RET_ONREMOVE) {
if (fsize1 / 5 < fsize2 / 6) {
goto not_so_good;
} else {
- dvr_entry_cancel_delete(de2, 1, 1);
+ dvr_entry_cancel_delete(de2, 1);
}
} else if (de->de_sched_state == DVR_COMPLETED) {
if(dvr_get_filesize(de, 0) == -1) {
delete_me:
- dvr_entry_cancel_delete(de, 0, 1);
+ dvr_entry_cancel_delete(de, 0);
dvr_entry_rerecord(de2);
return 1;
}
// if duplicate, then delete it now, don't record!
if (_dvr_duplicate_event(de)) {
- dvr_entry_cancel_delete(de, 1, 1);
+ dvr_entry_cancel_delete(de, 1);
return;
}
dvr_entry_class_delete(idnode_t *self)
{
dvr_entry_t *de = (dvr_entry_t *)self;
- dvr_entry_cancel_delete(de, 0, 0);
+ dvr_entry_cancel_delete(de, 0);
}
static int
if (cmd == 0 && !de->de_dont_rerecord) {
de->de_dont_rerecord = 1;
if (de->de_child)
- dvr_entry_cancel_delete(de->de_child, 0, 1);
+ dvr_entry_cancel_delete(de->de_child, 0);
} else {
de->de_dont_rerecord = 0;
dvr_entry_rerecord(de);
}
/**
- *
+ * Cancels an upcoming or active recording
*/
dvr_entry_t *
dvr_entry_cancel(dvr_entry_t *de, int rerecord)
case DVR_RECORDING:
dvr_stop_recording(de, SM_CODE_ABORTED, 1, 0);
break;
-
- case DVR_SCHEDULED:
+ /* Cancel is not valid for finished recordings */
case DVR_COMPLETED:
case DVR_MISSED_TIME:
+ return de;
+ case DVR_SCHEDULED:
case DVR_NOSTATE:
dvr_entry_destroy(de, 1);
de = NULL;
}
/**
- *
+ * Called by 'dvr_entry_cancel_remove' and 'dvr_entry_cancel_delete'
+ * delete = 0 -> remove finished and active recordings (visible as removed)
+ * delete = 1 -> delete finished and active recordings (not visible anymore)
*/
-void
-dvr_entry_cancel_delete(dvr_entry_t *de, int rerecord, int forcedestroy)
+static void
+dvr_entry_cancel_delete_remove(dvr_entry_t *de, int rerecord, int _delete)
{
dvr_entry_t *parent = de->de_parent;
dvr_autorec_entry_t *dae = de->de_autorec;
+ int save;
switch(de->de_sched_state) {
case DVR_RECORDING:
dvr_stop_recording(de, SM_CODE_ABORTED, 1, 0);
+ case DVR_MISSED_TIME:
case DVR_COMPLETED:
- dvr_entry_delete(de);
- if (forcedestroy)
- dvr_entry_destroy(de, 1);
- else
- dvr_entry_trydestroy(de);
+ save = dvr_entry_delete(de); /* Remove files */
+ if (_delete || dvr_entry_get_retention_days(de) == DVR_RET_ONREMOVE)
+ dvr_entry_destroy(de, 1); /* Delete database */
+ else if (save) {
+ idnode_changed(&de->de_id);
+ htsp_dvr_entry_update(de);
+ }
break;
-
case DVR_SCHEDULED:
- case DVR_MISSED_TIME:
case DVR_NOSTATE:
dvr_entry_destroy(de, 1);
break;
}
/**
- * Destroy db entry if possible.
- * The deletion of the db entry can be prevented by the minimal retention setting.
- * Prevention is needed in order to keep duplicate detection happy.
+ * Upcoming recording -> cancel
+ * Active recording -> cancel + remove
+ * Finished recordings -> remove
+ * The latter 2 will be visible as "removed recording"
*/
void
-dvr_entry_trydestroy(dvr_entry_t *de)
+dvr_entry_cancel_remove(dvr_entry_t *de, int rerecord)
{
- char ubuf[UUID_HEX_SIZE];
- uint32_t minretention, removal;
-
- if (!de->de_config || de->de_config->dvr_retention_minimal == DVR_RET_MIN_DISABLED)
- dvr_entry_destroy(de, 1);
- else {
- minretention = time_t_out_of_range((int64_t)de->de_stop + de->de_config->dvr_retention_minimal * (int64_t)86400);
- if (minretention < gclk()) /* Minimal retention period expired -> deleting db entry allowed */
- dvr_entry_destroy(de, 1);
- else {
- removal = (gclk() - (int64_t)de->de_stop)/(int64_t)86400;
-
- de->de_dont_reschedule = 1;
- de->de_removal = removal > DVR_RET_REM_DVRCONFIG ?
- removal : DVR_RET_REM_1DAY; /* Update removal to the current value */
- de->de_retention = de->de_config->dvr_retention_minimal; /* Update the retention to the minimum allowed value */
- idnode_changed(&de->de_id);
- dvr_entry_retention_timer(de); /* Rearm timer as retention was changed */
+ dvr_entry_cancel_delete_remove(de, rerecord, 0);
+}
- tvhinfo(LS_DVR, "delete entry %s not allowed \"%s\" on \"%s\", current retention period %"PRIu32", "
- "minimal retention period %"PRIu32"",
- idnode_uuid_as_str(&de->de_id, ubuf),
- lang_str_get(de->de_title, NULL), DVR_CH_NAME(de),
- removal, de->de_config->dvr_retention_minimal);
- }
- }
+/**
+ * Upcoming recording -> cancel
+ * Active recording -> abort + delete
+ * Finished recordings -> delete
+ * The latter 2 will NOT be visible as "removed recording"
+ */
+void
+dvr_entry_cancel_delete(dvr_entry_t *de, int rerecord)
+{
+ dvr_entry_cancel_delete_remove(de, rerecord, 1);
}
/**
lang_str_get(oldest->de_title, NULL), tbuf, TOMIB(fileSize));
dvr_disk_space_config_lastdelete = mclk();
- if (dvr_entry_get_retention_days(oldest) == DVR_RET_ONREMOVE) {
- dvr_entry_delete(oldest); // delete actual file
- dvr_entry_destroy(oldest, 1); // also delete database entry
- } else {
- if (dvr_entry_delete(oldest)) // delete actual file
- idnode_changed(&oldest->de_id);
- }
+ dvr_entry_cancel_remove(oldest, 0); /* Remove stored files and mark as "removed" */
} else {
tvhwarn(LS_DVR, "%s \"until space needed\" recordings found for config \"%s\", you are running out of disk space very soon!",
loops > 0 ? "Not enough" : "No", configName);
if (de == NULL)
return out;
- dvr_entry_cancel_delete(de, 0, 0);
+ dvr_entry_cancel_remove(de, 0);
return htsp_success();
}
}
}
};
+
+ var removeButton = {
+ name: 'remove',
+ builder: function() {
+ return new Ext.Toolbar.Button({
+ tooltip: _('Remove the selected recording from storage'),
+ iconCls: 'remove',
+ text: _('Remove'),
+ disabled: true
+ });
+ },
+ callback: function(conf, e, store, select) {
+ var r = select.getSelections();
+ if (r && r.length > 0) {
+ var uuids = [];
+ for (var i = 0; i < r.length; i++)
+ uuids.push(r[i].id);
+ tvheadend.AjaxConfirm({
+ url: 'api/dvr/entry/remove',
+ params: {
+ uuid: Ext.encode(uuids)
+ },
+ success: function(d) {
+ store.reload();
+ },
+ question: _('Do you really want to remove the selected recordings from storage?')
+ });
+ }
+ }
+ };
function selected(s, abuttons) {
var r = s.getSelections();
abuttons.download.setDisabled(!b);
abuttons.rerecord.setDisabled(!b);
abuttons.move.setDisabled(!b);
+ abuttons.remove.setDisabled(!b);
}
tvheadend.idnode_grid(panel, {
iconCls: 'finishedRec',
tabIndex: index,
edit: { params: { list: tvheadend.admin ? "owner,retention,removal,comment" : "comment" } },
- del: true,
- delquestion: _('Do you really want to delete the selected recordings?') + '<br/><br/>' +
- _('The associated file will be removed from storage.'),
+ del: false,
list: 'disp_title,disp_subtitle,episode,start_real,stop_real,' +
'duration,filesize,channelname,owner,creator,' +
'config_name,sched_status,errors,data_errors,url,comment',
return tvheadend.playLink('play/dvrfile/' + r.id, title);
}
}],
- tbar: [downloadButton, rerecordButton, moveButton],
+ tbar: [removeButton, downloadButton, rerecordButton, moveButton],
selected: selected
});
return panel;
};
+/**
+ *
+ */
+tvheadend.dvr_removed = function(panel, index) {
+
+ var actions = tvheadend.dvrRowActions();
+
+ var rerecordButton = {
+ name: 'rerecord',
+ builder: function() {
+ return new Ext.Toolbar.Button({
+ tooltip: _('Toggle re-record functionality'),
+ iconCls: 'rerecord',
+ text: _('Re-record'),
+ disabled: true
+ });
+ },
+ callback: function(conf, e, store, select) {
+ var r = select.getSelections();
+ if (r && r.length > 0) {
+ var uuids = [];
+ for (var i = 0; i < r.length; i++)
+ uuids.push(r[i].id);
+ tvheadend.Ajax({
+ url: 'api/dvr/entry/rerecord/toggle',
+ params: {
+ uuid: Ext.encode(uuids)
+ },
+ success: function(d) {
+ store.reload();
+ }
+ });
+ }
+ }
+ };
+
+ function selected(s, abuttons) {
+ var r = s.getSelections();
+ abuttons.rerecord.setDisabled(r.length <= 0);
+ }
+
+ tvheadend.idnode_grid(panel, {
+ url: 'api/dvr/entry',
+ gridURL: 'api/dvr/entry/grid_removed',
+ readonly: true,
+ titleS: _('Removed Recording'),
+ titleP: _('Removed Recordings'),
+ iconCls: 'remove',
+ tabIndex: index,
+ uilevel: 'expert',
+ edit: { params: { list: tvheadend.admin ? "retention,owner,comment" : "retention,comment" } },
+ del: true,
+ list: 'disp_title,disp_subtitle,episode,start_real,stop_real,' +
+ 'duration,channelname,owner,creator,config_name,' +
+ 'sched_status,errors,data_errors,url,comment',
+ sort: {
+ field: 'start_real',
+ direction: 'ASC'
+ },
+ plugins: [actions],
+ lcol: [actions],
+ tbar: [rerecordButton],
+ selected: selected
+ });
+
+ return panel;
+};
+
/**
*
*/
tvheadend.dvr_upcoming(p, 0);
tvheadend.dvr_finished(p, 1);
tvheadend.dvr_failed(p, 2);
- tvheadend.autorec_editor(p, 3);
- tvheadend.timerec_editor(p, 4);
+ tvheadend.dvr_removed(p, 3);
+ tvheadend.autorec_editor(p, 4);
+ tvheadend.timerec_editor(p, 5);
return p;
}