From b46cf4b920984492df598c1b61179acfc7f6f22e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 17 Nov 2021 16:04:53 +0100 Subject: Add ability to remove hls/webtorrent files --- .../overview/videos/video-list.component.html | 4 +- .../+admin/overview/videos/video-list.component.ts | 55 +++++++++++++++++----- .../app/shared/shared-main/video/video.model.ts | 11 ++++- .../app/shared/shared-main/video/video.service.ts | 9 ++++ .../video-actions-dropdown.component.ts | 39 ++++++++++++++- 5 files changed, 101 insertions(+), 17 deletions(-) (limited to 'client/src/app') diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html index 9b536ec11..6e4fb4c6f 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.html +++ b/client/src/app/+admin/overview/videos/video-list.component.html @@ -57,7 +57,7 @@ @@ -127,4 +127,4 @@ - + diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts index 7f268bb23..3c21adb44 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.ts +++ b/client/src/app/+admin/overview/videos/video-list.component.ts @@ -39,7 +39,8 @@ export class VideoListComponent extends RestTable implements OnInit { report: false, duplicate: true, mute: true, - liveInfo: false + liveInfo: false, + removeFiles: true } loading = true @@ -71,17 +72,34 @@ export class VideoListComponent extends RestTable implements OnInit { { label: $localize`Delete`, handler: videos => this.removeVideos(videos), - isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO) + isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO), + iconName: 'delete' }, { label: $localize`Block`, handler: videos => this.videoBlockModal.show(videos), - isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => !v.blacklisted) + isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => !v.blacklisted), + iconName: 'no' }, { label: $localize`Unblock`, handler: videos => this.unblockVideos(videos), - isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => v.blacklisted) + isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => v.blacklisted), + iconName: 'undo' + } + ], + [ + { + label: $localize`Delete HLS files`, + handler: videos => this.removeVideoFiles(videos, 'hls'), + isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_FILES) && videos.every(v => v.hasHLS() && v.hasWebTorrent()), + iconName: 'delete' + }, + { + label: $localize`Delete WebTorrent files`, + handler: videos => this.removeVideoFiles(videos, 'webtorrent'), + isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_FILES) && videos.every(v => v.hasHLS() && v.hasWebTorrent()), + iconName: 'delete' } ] ] @@ -95,10 +113,6 @@ export class VideoListComponent extends RestTable implements OnInit { return this.selectedVideos.length !== 0 } - onVideoRemoved () { - this.reloadData() - } - getPrivacyBadgeClass (video: Video) { if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green' @@ -146,11 +160,7 @@ export class VideoListComponent extends RestTable implements OnInit { return files.reduce((p, f) => p += f.size, 0) } - onVideoBlocked () { - this.reloadData() - } - - protected reloadData () { + reloadData () { this.selectedVideos = [] this.loading = true @@ -197,4 +207,23 @@ export class VideoListComponent extends RestTable implements OnInit { error: err => this.notifier.error(err.message) }) } + + private async removeVideoFiles (videos: Video[], type: 'hls' | 'webtorrent') { + const message = type === 'hls' + ? $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?` + : $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?` + + const res = await this.confirmService.confirm(message, $localize`Delete`) + if (res === false) return + + this.videoService.removeVideoFiles(videos.map(v => v.id), type) + .subscribe({ + next: () => { + this.notifier.success($localize`Files were removed.`) + this.reloadData() + }, + + error: err => this.notifier.error(err.message) + }) + } } diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index 472a8c810..4203ff1c0 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts @@ -14,7 +14,8 @@ import { VideoPrivacy, VideoScheduleUpdate, VideoState, - VideoStreamingPlaylist + VideoStreamingPlaylist, + VideoStreamingPlaylistType } from '@shared/models' export class Video implements VideoServerModel { @@ -219,6 +220,14 @@ export class Video implements VideoServerModel { return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO)) } + hasHLS () { + return this.streamingPlaylists?.some(p => p.type === VideoStreamingPlaylistType.HLS) + } + + hasWebTorrent () { + return this.files && this.files.length !== 0 + } + isLiveInfoAvailableBy (user: AuthUser) { return this.isLive && user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.GET_ANY_LIVE)) diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 570e8e3be..d135a27dc 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts @@ -299,6 +299,15 @@ export class VideoService { ) } + removeVideoFiles (videoIds: (number | string)[], type: 'hls' | 'webtorrent') { + return from(videoIds) + .pipe( + concatMap(id => this.authHttp.delete(VideoService.BASE_VIDEO_URL + '/' + id + '/' + type)), + toArray(), + catchError(err => this.restExtractor.handleError(err)) + ) + } + loadCompleteDescription (descriptionPath: string) { return this.authHttp .get<{ description: string }>(environment.apiUrl + descriptionPath) diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts index eff56b40e..82c084791 100644 --- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@a import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' import { BlocklistService, VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' -import { VideoCaption } from '@shared/models' +import { UserRight, VideoCaption } from '@shared/models' import { Actor, DropdownAction, @@ -27,6 +27,7 @@ export type VideoActionsDisplayType = { duplicate?: boolean mute?: boolean liveInfo?: boolean + removeFiles?: boolean } @Component({ @@ -65,6 +66,7 @@ export class VideoActionsDropdownComponent implements OnChanges { @Input() buttonSize: DropdownButtonSize = 'normal' @Input() buttonDirection: DropdownDirection = 'vertical' + @Output() videoFilesRemoved = new EventEmitter() @Output() videoRemoved = new EventEmitter() @Output() videoUnblocked = new EventEmitter() @Output() videoBlocked = new EventEmitter() @@ -174,6 +176,10 @@ export class VideoActionsDropdownComponent implements OnChanges { return this.video.account.id !== this.user.account.id } + canRemoveVideoFiles () { + return this.user.hasRight(UserRight.MANAGE_VIDEO_FILES) && this.video.hasHLS() && this.video.hasWebTorrent() + } + /* Action handlers */ async unblockVideo () { @@ -245,6 +251,23 @@ export class VideoActionsDropdownComponent implements OnChanges { }) } + async removeVideoFiles (video: Video, type: 'hls' | 'webtorrent') { + const confirmMessage = $localize`Do you really want to remove "${this.video.name}" files?` + + const res = await this.confirmService.confirm(confirmMessage, $localize`Remove "${this.video.name}" files`) + if (res === false) return + + this.videoService.removeVideoFiles([ video.id ], type) + .subscribe({ + next: () => { + this.notifier.success($localize`Removed files of ${video.name}.`) + this.videoFilesRemoved.emit() + }, + + error: err => this.notifier.error(err.message) + }) + } + onVideoBlocked () { this.videoBlocked.emit() } @@ -317,6 +340,20 @@ export class VideoActionsDropdownComponent implements OnChanges { iconName: 'flag' } ], + [ + { + label: $localize`Delete HLS files`, + handler: ({ video }) => this.removeVideoFiles(video, 'hls'), + isDisplayed: () => this.displayOptions.removeFiles && this.canRemoveVideoFiles(), + iconName: 'delete' + }, + { + label: $localize`Delete WebTorrent files`, + handler: ({ video }) => this.removeVideoFiles(video, 'webtorrent'), + isDisplayed: () => this.displayOptions.removeFiles && this.canRemoveVideoFiles(), + iconName: 'delete' + } + ], [ // actions regarding the account/its server { label: $localize`Mute account`, -- cgit v1.2.3