Added ability to manually check a subscription, and to cancel a subscription check

This commit is contained in:
Isaac Abadi
2023-11-26 01:16:54 -05:00
parent 9206d4ba28
commit 2aaea00976
13 changed files with 217 additions and 25 deletions

View File

@@ -293,6 +293,48 @@ paths:
$ref: '#/components/schemas/UnsubscribeResponse'
security:
- Auth query parameter: []
/api/checkSubscription:
post:
tags:
- subscriptions
summary: Run a check for videos for a subscription
description: Runs a subscription check
operationId: post-api-checksubscription
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CheckSubscriptionRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessObject'
security:
- Auth query parameter: []
/api/cancelCheckSubscription:
post:
tags:
- subscriptions
summary: Cancels check for videos for a subscription
description: Cancels subscription check
operationId: post-api-checksubscription
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CheckSubscriptionRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessObject'
security:
- Auth query parameter: []
/api/deleteSubscriptionFile:
post:
tags:
@@ -1981,11 +2023,11 @@ components:
type: string
UnsubscribeRequest:
required:
- sub
- sub_id
type: object
properties:
sub:
$ref: '#/components/schemas/SubscriptionRequestData'
sub_id:
type: string
deleteMode:
type: boolean
description: Defaults to false
@@ -1998,6 +2040,13 @@ components:
type: boolean
error:
type: string
CheckSubscriptionRequest:
required:
- sub_id
type: object
properties:
sub_id:
type: string
DeleteAllFilesResponse:
type: object
properties:
@@ -2843,6 +2892,8 @@ components:
nullable: true
isPlaylist:
type: boolean
child_process:
type: object
archive:
type: string
timerange:
@@ -2851,6 +2902,10 @@ components:
type: string
custom_output:
type: string
downloading:
type: boolean
paused:
type: boolean
videos:
type: array
items:

View File

@@ -534,7 +534,7 @@ async function loadConfig() {
// set downloading to false
let subscriptions = await subscriptions_api.getAllSubscriptions();
subscriptions.forEach(async sub => subscriptions_api.writeSubscriptionMetadata(sub));
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false});
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false, child_process: null});
// runs initially, then runs every ${subscriptionCheckInterval} seconds
const watchSubscriptionsInterval = function() {
watchSubscriptions();
@@ -1196,10 +1196,10 @@ app.post('/api/subscribe', optionalJwt, async (req, res) => {
app.post('/api/unsubscribe', optionalJwt, async (req, res) => {
let deleteMode = req.body.deleteMode
let sub = req.body.sub;
let sub_id = req.body.sub_id;
let user_uid = req.isAuthenticated() ? req.user.uid : null;
let result_obj = subscriptions_api.unsubscribe(sub, deleteMode, user_uid);
let result_obj = subscriptions_api.unsubscribe(sub_id, deleteMode, user_uid);
if (result_obj.success) {
res.send({
success: result_obj.success
@@ -1289,6 +1289,36 @@ app.post('/api/updateSubscription', optionalJwt, async (req, res) => {
});
});
app.post('/api/checkSubscription', optionalJwt, async (req, res) => {
let sub_id = req.body.sub_id;
let user_uid = req.isAuthenticated() ? req.user.uid : null;
const success = subscriptions_api.getVideosForSub(sub_id, user_uid);
res.send({
success: success
});
});
app.post('/api/cancelCheckSubscription', optionalJwt, async (req, res) => {
let sub_id = req.body.sub_id;
let user_uid = req.isAuthenticated() ? req.user.uid : null;
const success = subscriptions_api.cancelCheckSubscription(sub_id, user_uid);
res.send({
success: success
});
});
app.post('/api/cancelSubscriptionCheck', optionalJwt, async (req, res) => {
let sub_id = req.body.sub_id;
let user_uid = req.isAuthenticated() ? req.user.uid : null;
const success = subscriptions_api.getVideosForSub(sub_id, user_uid);
res.send({
success: success
});
});
app.post('/api/getSubscriptions', optionalJwt, async (req, res) => {
let user_uid = req.isAuthenticated() ? req.user.uid : null;

View File

@@ -139,7 +139,7 @@ exports.clearDownload = async (download_uid) => {
async function handleDownloadError(download_uid, error_message, error_type = null) {
if (!download_uid) return;
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (download['error']) return;
if (!download || download['error']) return;
notifications_api.sendDownloadErrorNotification(download, download['user_uid'], error_message, error_type);
await db_api.updateRecord('download_queue', {uid: download['uid']}, {error: error_message, finished: true, running: false, error_type: error_type});
}
@@ -332,7 +332,7 @@ exports.downloadQueuedFile = async(download_uid, customDownloadHandler = null) =
logger.debug(`${type === 'audio' ? 'Audio' : 'Video'} download delay: ${difference} seconds.`);
if (!parsed_output) {
const errored_download = await db_api.getRecord('download_queue', {uid: download_uid});
if (errored_download['paused']) return;
if (errored_download && errored_download['paused']) return;
logger.error(err.toString());
await handleDownloadError(download_uid, err.toString(), 'unknown_error');
resolve(false);
@@ -599,6 +599,7 @@ async function checkDownloadPercent(download_uid) {
*/
const download = await db_api.getRecord('download_queue', {uid: download_uid});
if (!download) return;
const files_to_check_for_progress = download['files_to_check_for_progress'];
const resulting_file_size = download['expected_file_size'];

View File

@@ -39,7 +39,7 @@ exports.subscribe = async (sub, user_uid = null, skip_get_info = false) => {
exports.writeSubscriptionMetadata(sub);
if (success) {
if (!sub.paused) exports.getVideosForSub(sub, user_uid);
if (!sub.paused) exports.getVideosForSub(sub.id);
} else {
logger.error('Subscribe: Failed to get subscription info. Subscribe failed.')
}
@@ -96,7 +96,8 @@ async function getSubscriptionInfo(sub) {
return false;
}
exports.unsubscribe = async (sub, deleteMode, user_uid = null) => {
exports.unsubscribe = async (sub_id, deleteMode, user_uid = null) => {
const sub = await exports.getSubscription(sub_id);
let basePath = null;
if (user_uid)
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
@@ -119,6 +120,7 @@ exports.unsubscribe = async (sub, deleteMode, user_uid = null) => {
}
}
await killSubDownloads(sub_id, true);
await db_api.removeRecord('subscriptions', {id: id});
await db_api.removeAllRecords('files', {sub_id: id});
@@ -203,12 +205,18 @@ exports.deleteSubscriptionFile = async (sub, file, deleteForever, file_uid = nul
}
}
exports.getVideosForSub = async (sub, user_uid = null) => {
const latest_sub_obj = await exports.getSubscription(sub.id);
if (!latest_sub_obj || latest_sub_obj['downloading']) {
exports.getVideosForSub = async (sub_id) => {
const sub = await exports.getSubscription(sub_id);
if (!sub || sub['downloading']) {
return false;
}
_getVideosForSub(sub);
return true;
}
async function _getVideosForSub(sub) {
const user_uid = sub['user_uid'];
updateSubscriptionProperty(sub, {downloading: true}, user_uid);
// get basePath
@@ -226,7 +234,8 @@ exports.getVideosForSub = async (sub, user_uid = null) => {
// get videos
logger.verbose(`Subscription: getting list of videos to download for ${sub.name} with args: ${downloadConfig.join(',')}`);
let {callback} = await youtubedl_api.runYoutubeDL(sub.url, downloadConfig);
let {child_process, callback} = await youtubedl_api.runYoutubeDL(sub.url, downloadConfig);
updateSubscriptionProperty(sub, {child_process: child_process}, user_uid);
const {parsed_output, err} = await callback;
updateSubscriptionProperty(sub, {downloading: false, child_process: null}, user_uid);
if (!parsed_output) {
@@ -396,8 +405,37 @@ async function getFilesToDownload(sub, output_jsons) {
return files_to_download;
}
exports.cancelCheckSubscription = async (sub_id) => {
const sub = await exports.getSubscription(sub_id);
if (!sub['downloading'] && !sub['child_process']) {
logger.error('Failed to cancel subscription check, verify that it is still running!');
return false;
}
// if check is ongoing
if (sub['child_process']) {
const child_process = sub['child_process'];
youtubedl_api.killYoutubeDLProcess(child_process);
}
// cancel activate video downloads
await killSubDownloads(sub_id);
return true;
}
async function killSubDownloads(sub_id, remove_downloads = false) {
const sub_downloads = await db_api.getRecords('download_queue', {sub_id: sub_id});
for (const sub_download of sub_downloads) {
if (sub_download['running'])
await downloader_api.cancelDownload(sub_download['uid']);
if (remove_downloads)
await db_api.removeRecord('download_queue', {uid: sub_download['uid']});
}
}
exports.getSubscriptions = async (user_uid = null) => {
// TODO: fix issue where the downloading property may not match getSubscription()
return await db_api.getRecords('subscriptions', {user_uid: user_uid});
}

View File

@@ -14,6 +14,7 @@ export type { ChangeRolePermissionsRequest } from './models/ChangeRolePermission
export type { ChangeUserPermissionsRequest } from './models/ChangeUserPermissionsRequest';
export type { CheckConcurrentStreamRequest } from './models/CheckConcurrentStreamRequest';
export type { CheckConcurrentStreamResponse } from './models/CheckConcurrentStreamResponse';
export type { CheckSubscriptionRequest } from './models/CheckSubscriptionRequest';
export type { ClearDownloadsRequest } from './models/ClearDownloadsRequest';
export type { ConcurrentStream } from './models/ConcurrentStream';
export type { Config } from './models/Config';

View File

@@ -0,0 +1,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CheckSubscriptionRequest = {
sub_id: string;
};

View File

@@ -11,9 +11,12 @@ export type Subscription = {
type: FileType;
user_uid: string | null;
isPlaylist: boolean;
child_process?: any;
archive?: string;
timerange?: string;
custom_args?: string;
custom_output?: string;
downloading?: boolean;
paused?: boolean;
videos: Array<any>;
};

View File

@@ -2,10 +2,8 @@
/* tslint:disable */
/* eslint-disable */
import type { SubscriptionRequestData } from './SubscriptionRequestData';
export type UnsubscribeRequest = {
sub: SubscriptionRequestData;
sub_id: string;
/**
* Defaults to false
*/

View File

@@ -2,6 +2,7 @@ import { Component, OnInit, Inject } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { PostsService } from 'app/posts.services';
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component';
import { Subscription } from 'api-types';
@Component({
selector: 'app-subscription-info-dialog',
@@ -10,7 +11,7 @@ import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.compone
})
export class SubscriptionInfoDialogComponent implements OnInit {
sub = null;
sub: Subscription = null;
unsubbedEmitter = null;
constructor(public dialogRef: MatDialogRef<SubscriptionInfoDialogComponent>,
@@ -41,7 +42,7 @@ export class SubscriptionInfoDialogComponent implements OnInit {
}
unsubscribe() {
this.postsService.unsubscribe(this.sub, true).subscribe(res => {
this.postsService.unsubscribe(this.sub.id, true).subscribe(res => {
this.unsubbedEmitter.emit(true);
this.dialogRef.close();
});

View File

@@ -113,7 +113,8 @@ import {
Archive,
Subscription,
RestartDownloadResponse,
TaskType
TaskType,
CheckSubscriptionRequest
} from '../api-types';
import { isoLangs } from './dialogs/user-profile-dialog/locales_list';
import { Title } from '@angular/platform-browser';
@@ -566,8 +567,18 @@ export class PostsService implements CanActivate {
return this.http.post<SuccessObject>(this.path + 'updateSubscription', {subscription: subscription}, this.httpOptions);
}
unsubscribe(sub: SubscriptionRequestData, deleteMode = false) {
const body: UnsubscribeRequest = {sub: sub, deleteMode: deleteMode};
checkSubscription(sub_id: string) {
const body: CheckSubscriptionRequest = {sub_id: sub_id};
return this.http.post<SuccessObject>(this.path + 'checkSubscription', body, this.httpOptions);
}
cancelCheckSubscription(sub_id: string) {
const body: CheckSubscriptionRequest = {sub_id: sub_id};
return this.http.post<SuccessObject>(this.path + 'cancelCheckSubscription', body, this.httpOptions);
}
unsubscribe(sub_id: string, deleteMode = false) {
const body: UnsubscribeRequest = {sub_id: sub_id, deleteMode: deleteMode};
return this.http.post<UnsubscribeResponse>(this.path + 'unsubscribe', body, this.httpOptions)
}

View File

@@ -3,6 +3,7 @@
<div style="margin-bottom: 15px;">
<h2 style="text-align: center;" *ngIf="subscription">
{{subscription.name}}&nbsp;<ng-container *ngIf="subscription.paused" i18n="Paused suffix">(Paused)</ng-container>
<button class="edit-button" (click)="editSubscription()" [disabled]="downloading" matTooltip="Edit" i18n-matTooltip="Edit" mat-icon-button><mat-icon class="save-icon">edit</mat-icon></button>
</h2>
<mat-progress-bar style="width: 80%; margin: 0 auto; margin-top: 15px;" *ngIf="subscription && subscription.downloading" mode="indeterminate"></mat-progress-bar>
</div>
@@ -13,7 +14,14 @@
<div style="margin-bottom: 100px;" *ngIf="subscription">
<app-recent-videos #recentVideos [sub_id]="subscription.id"></app-recent-videos>
</div>
<button class="edit-button" color="primary" (click)="editSubscription()" [disabled]="downloading" matTooltip="Edit" i18n-matTooltip="Edit" mat-fab><mat-icon class="save-icon">edit</mat-icon></button>
<div class="check-button">
<ng-container *ngIf="subscription.downloading">
<button color="primary" (click)="cancelCheckSubscription()" [disabled]="cancel_clicked" matTooltip="Cancel subscription check" i18n-matTooltip="Cancel subscription check" mat-fab><mat-icon class="save-icon">cancel</mat-icon></button>
</ng-container>
<ng-container *ngIf="!subscription.downloading">
<button color="primary" (click)="checkSubscription()" [disabled]="check_clicked" matTooltip="Check subscription" i18n-matTooltip="Check subscription" mat-fab><mat-icon class="save-icon">youtube_searched_for</mat-icon></button>
</ng-container>
</div>
<button class="watch-button" color="primary" (click)="watchSubscription()" matTooltip="Play all" i18n-matTooltip="Play all" mat-fab><mat-icon class="save-icon">video_library</mat-icon></button>
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" matTooltip="Download zip" i18n-matTooltip="Download zip" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
</div>

View File

@@ -58,13 +58,19 @@
bottom: 25px;
}
.edit-button {
.check-button {
left: 25px;
position: fixed;
bottom: 25px;
z-index: 99999;
}
.edit-button {
right: 35px;
position: fixed;
z-index: 99999;
}
.save-icon {
bottom: 1px;
position: relative;

View File

@@ -3,6 +3,7 @@ import { PostsService } from 'app/posts.services';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component';
import { Subscription } from 'api-types';
@Component({
selector: 'app-subscription',
@@ -12,11 +13,13 @@ import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-d
export class SubscriptionComponent implements OnInit, OnDestroy {
id = null;
subscription = null;
subscription: Subscription = null;
use_youtubedl_archive = false;
descendingMode = true;
downloading = false;
sub_interval = null;
check_clicked = false;
cancel_clicked = false;
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router, private dialog: MatDialog) { }
@@ -90,4 +93,34 @@ export class SubscriptionComponent implements OnInit, OnDestroy {
this.router.navigate(['/player', {sub_id: this.subscription.id}])
}
checkSubscription(): void {
this.check_clicked = true;
this.postsService.checkSubscription(this.subscription.id).subscribe(res => {
this.check_clicked = false;
if (!res['success']) {
this.postsService.openSnackBar('Failed to check subscription!');
return;
}
}, err => {
console.error(err);
this.check_clicked = false;
this.postsService.openSnackBar('Failed to check subscription!');
});
}
cancelCheckSubscription(): void {
this.cancel_clicked = true;
this.postsService.cancelCheckSubscription(this.subscription.id).subscribe(res => {
this.cancel_clicked = false;
if (!res['success']) {
this.postsService.openSnackBar('Failed to cancel check subscription!');
return;
}
}, err => {
console.error(err);
this.cancel_clicked = false;
this.postsService.openSnackBar('Failed to cancel check subscription!');
});
}
}