<ng-template pTemplate="header">
<tr>
<th style="width: 40px;"></th>
- <th style="width: 70px">Action</th>
+ <th style="width: 200px">Action</th>
<th style="width: 45%" i18n>Target</th>
<th style="width: 55%" i18n>Video</th>
<th style="width: 150px" i18n>State</th>
</td>
<td class="action-cell">
- <my-edit-button *ngIf="isVideoImportSuccess(videoImport) && videoImport.video"
- [routerLink]="getEditVideoUrl(videoImport.video)"></my-edit-button>
+ <my-button *ngIf="isVideoImportPending(videoImport)" i18n-label label="Cancel" icon="no" (click)="cancelImport(videoImport)"></my-button>
+ <my-delete-button *ngIf="isVideoImportFailed(videoImport) || isVideoImportCancelled(videoImport) || !videoImport.video" (click)="deleteImport(videoImport)"></my-delete-button>
+ <my-edit-button *ngIf="isVideoImportSuccess(videoImport) && videoImport.video" [routerLink]="getEditVideoUrl(videoImport.video)"></my-edit-button>
</td>
<td>
return 'badge-banned'
case VideoImportState.PENDING:
return 'badge-yellow'
+ case VideoImportState.PROCESSING:
+ return 'badge-blue'
default:
return 'badge-green'
}
return videoImport.state.id === VideoImportState.FAILED
}
+ isVideoImportCancelled (videoImport: VideoImport) {
+ return videoImport.state.id === VideoImportState.CANCELLED
+ }
+
getVideoUrl (video: { uuid: string }) {
return Video.buildWatchUrl(video)
}
return Video.buildUpdateUrl(video)
}
+ deleteImport (videoImport: VideoImport) {
+ this.videoImportService.deleteVideoImport(videoImport)
+ .subscribe({
+ next: () => this.reloadData(),
+
+ error: err => this.notifier.error(err.message)
+ })
+ }
+
+ cancelImport (videoImport: VideoImport) {
+ this.videoImportService.cancelVideoImport(videoImport)
+ .subscribe({
+ next: () => this.reloadData(),
+
+ error: err => this.notifier.error(err.message)
+ })
+ }
+
protected reloadData () {
this.videoImportService.getMyVideoImports(this.pagination, this.sort)
.subscribe({
-<span class="action-button" [ngClass]="getClasses()" [ngbTooltip]="getTitle()" tabindex="0">
- <my-global-icon *ngIf="!loading" [iconName]="icon"></my-global-icon>
+<span class="action-button" [ngClass]="getClasses()" [ngbTooltip]="title" tabindex="0">
+ <my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon>
<my-small-loader [loading]="loading"></my-small-loader>
<span *ngIf="label" class="button-label">{{ label }}</span>
@Input() disabled = false
@Input() responsiveLabel = false
- getTitle () {
- return this.title || this.label
- }
-
getClasses () {
return {
[this.className]: true,
// <my-delete-button label /> Use default label
if (this.label === '') {
this.label = $localize`Delete`
-
- if (!this.title) {
- this.title = this.label
- }
}
}
}
)
}
+ deleteVideoImport (videoImport: VideoImport) {
+ return this.authHttp.delete(VideoImportService.BASE_VIDEO_IMPORT_URL + videoImport.id)
+ .pipe(catchError(err => this.restExtractor.handleError(err)))
+ }
+
+ cancelVideoImport (videoImport: VideoImport) {
+ return this.authHttp.post(VideoImportService.BASE_VIDEO_IMPORT_URL + videoImport.id + '/cancel', {})
+ .pipe(catchError(err => this.restExtractor.handleError(err)))
+ }
+
private buildImportVideoObject (video: VideoUpdate): VideoImportCreate {
const language = video.language || null
const licence = video.licence || null
}
@mixin peertube-button {
- @include padding(0, 17px, 0, 13px);
+ padding: 0 13px;
border: 0;
font-weight: $font-semibold;
text-align: center;
cursor: pointer;
+
+ my-global-icon + * {
+ @include margin-right(4px);
+ }
}
@mixin peertube-button-link {
import express from 'express'
-import { Job, JobState, JobType, ResultList, UserRight } from '@shared/models'
+import { HttpStatusCode, Job, JobState, JobType, ResultList, UserRight } from '@shared/models'
import { isArray } from '../../helpers/custom-validators/misc'
import { JobQueue } from '../../lib/job-queue'
import {
const jobsRouter = express.Router()
+jobsRouter.post('/pause',
+ authenticate,
+ ensureUserHasRight(UserRight.MANAGE_JOBS),
+ asyncMiddleware(pauseJobQueue)
+)
+
+jobsRouter.post('/resume',
+ authenticate,
+ ensureUserHasRight(UserRight.MANAGE_JOBS),
+ asyncMiddleware(resumeJobQueue)
+)
+
jobsRouter.get('/:state?',
openapiOperationDoc({ operationId: 'getJobs' }),
authenticate,
// ---------------------------------------------------------------------------
+async function pauseJobQueue (req: express.Request, res: express.Response) {
+ await JobQueue.Instance.pause()
+
+ return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
+}
+
+async function resumeJobQueue (req: express.Request, res: express.Response) {
+ await JobQueue.Instance.resume()
+
+ return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
+}
+
async function listJobs (req: express.Request, res: express.Response) {
const state = req.params.state as JobState
const asc = req.query.sort === 'createdAt'
MVideoWithBlacklistLight
} from '@server/types/models'
import { MVideoImportFormattable } from '@server/types/models/video/video-import'
-import { ServerErrorCode, ThumbnailType, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '@shared/models'
+import {
+ HttpStatusCode,
+ ServerErrorCode,
+ ThumbnailType,
+ VideoImportCreate,
+ VideoImportState,
+ VideoPrivacy,
+ VideoState
+} from '@shared/models'
import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
import { isArray } from '../../../helpers/custom-validators/misc'
import { JobQueue } from '../../../lib/job-queue/job-queue'
import { updateVideoMiniatureFromExisting, updateVideoMiniatureFromUrl } from '../../../lib/thumbnail'
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
-import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
+import {
+ asyncMiddleware,
+ asyncRetryTransactionMiddleware,
+ authenticate,
+ videoImportAddValidator,
+ videoImportCancelValidator,
+ videoImportDeleteValidator
+} from '../../../middlewares'
import { VideoModel } from '../../../models/video/video'
import { VideoCaptionModel } from '../../../models/video/video-caption'
import { VideoImportModel } from '../../../models/video/video-import'
asyncRetryTransactionMiddleware(addVideoImport)
)
+videoImportsRouter.post('/imports/:id/cancel',
+ authenticate,
+ asyncMiddleware(videoImportCancelValidator),
+ asyncRetryTransactionMiddleware(cancelVideoImport)
+)
+
+videoImportsRouter.delete('/imports/:id',
+ authenticate,
+ asyncMiddleware(videoImportDeleteValidator),
+ asyncRetryTransactionMiddleware(deleteVideoImport)
+)
+
// ---------------------------------------------------------------------------
export {
// ---------------------------------------------------------------------------
+async function deleteVideoImport (req: express.Request, res: express.Response) {
+ const videoImport = res.locals.videoImport
+
+ await videoImport.destroy()
+
+ return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
+}
+
+async function cancelVideoImport (req: express.Request, res: express.Response) {
+ const videoImport = res.locals.videoImport
+
+ videoImport.state = VideoImportState.CANCELLED
+ await videoImport.save()
+
+ return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
+}
+
function addVideoImport (req: express.Request, res: express.Response) {
if (req.body.targetUrl) return addYoutubeDLImport(req, res)
[VideoImportState.FAILED]: 'Failed',
[VideoImportState.PENDING]: 'Pending',
[VideoImportState.SUCCESS]: 'Success',
- [VideoImportState.REJECTED]: 'Rejected'
+ [VideoImportState.REJECTED]: 'Rejected',
+ [VideoImportState.CANCELLED]: 'Cancelled',
+ [VideoImportState.PROCESSING]: 'Processing'
}
const ABUSE_STATES: { [ id in AbuseState ]: string } = {
async function processVideoImport (job: Job) {
const payload = job.data as VideoImportPayload
- if (payload.type === 'youtube-dl') return processYoutubeDLImport(job, payload)
- if (payload.type === 'magnet-uri' || payload.type === 'torrent-file') return processTorrentImport(job, payload)
+ const videoImport = await getVideoImportOrDie(payload.videoImportId)
+ if (videoImport.state === VideoImportState.CANCELLED) {
+ logger.info('Do not process import since it has been cancelled', { payload })
+ return
+ }
+
+ videoImport.state = VideoImportState.PROCESSING
+ await videoImport.save()
+
+ if (payload.type === 'youtube-dl') return processYoutubeDLImport(job, videoImport, payload)
+ if (payload.type === 'magnet-uri' || payload.type === 'torrent-file') return processTorrentImport(job, videoImport, payload)
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-async function processTorrentImport (job: Job, payload: VideoImportTorrentPayload) {
+async function processTorrentImport (job: Job, videoImport: MVideoImportDefault, payload: VideoImportTorrentPayload) {
logger.info('Processing torrent video import in job %d.', job.id)
- const videoImport = await getVideoImportOrDie(payload.videoImportId)
+ const options = { type: payload.type, videoImportId: payload.videoImportId }
- const options = {
- type: payload.type,
- videoImportId: payload.videoImportId
- }
const target = {
torrentName: videoImport.torrentName ? getSecureTorrentName(videoImport.torrentName) : undefined,
uri: videoImport.magnetUri
return processFile(() => downloadWebTorrentVideo(target, VIDEO_IMPORT_TIMEOUT), videoImport, options)
}
-async function processYoutubeDLImport (job: Job, payload: VideoImportYoutubeDLPayload) {
+async function processYoutubeDLImport (job: Job, videoImport: MVideoImportDefault, payload: VideoImportYoutubeDLPayload) {
logger.info('Processing youtubeDL video import in job %d.', job.id)
- const videoImport = await getVideoImportOrDie(payload.videoImportId)
- const options = {
- type: payload.type,
- videoImportId: videoImport.id
- }
+ const options = { type: payload.type, videoImportId: videoImport.id }
const youtubeDL = new YoutubeDLWrapper(videoImport.targetUrl, ServerConfigManager.Instance.getEnabledResolutions('vod'))
}
}
+ async pause () {
+ for (const handler of Object.keys(this.queues)) {
+ await this.queues[handler].pause(true)
+ }
+ }
+
+ async resume () {
+ for (const handler of Object.keys(this.queues)) {
+ await this.queues[handler].resume(true)
+ }
+ }
+
createJob (obj: CreateJobArgument, options: CreateJobOptions = {}): void {
this.createJobWithPromise(obj, options)
.catch(err => logger.error('Cannot create job.', { err, obj }))
import express from 'express'
-import { body } from 'express-validator'
+import { body, param } from 'express-validator'
+import { isValid as isIPValid, parse as parseIP } from 'ipaddr.js'
import { isPreImportVideoAccepted } from '@server/lib/moderation'
import { Hooks } from '@server/lib/plugins/hooks'
-import { HttpStatusCode } from '@shared/models'
+import { MUserAccountId, MVideoImport } from '@server/types/models'
+import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models'
import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model'
import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc'
import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports'
import { logger } from '../../../helpers/logger'
import { CONFIG } from '../../../initializers/config'
import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
-import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared'
+import { areValidationErrors, doesVideoChannelOfAccountExist, doesVideoImportExist } from '../shared'
import { getCommonVideoEditAttributes } from './videos'
-import { isValid as isIPValid, parse as parseIP } from 'ipaddr.js'
const videoImportAddValidator = getCommonVideoEditAttributes().concat([
body('channelId')
}
])
+const videoImportDeleteValidator = [
+ param('id')
+ .custom(isIdValid).withMessage('Should have correct import id'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking videoImportDeleteValidator parameters', { parameters: req.params })
+
+ if (areValidationErrors(req, res)) return
+
+ if (!await doesVideoImportExist(parseInt(req.params.id), res)) return
+ if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return
+
+ if (res.locals.videoImport.state === VideoImportState.PENDING) {
+ return res.fail({
+ status: HttpStatusCode.CONFLICT_409,
+ message: 'Cannot delete a pending video import. Cancel it or wait for the end of the import first.'
+ })
+ }
+
+ return next()
+ }
+]
+
+const videoImportCancelValidator = [
+ param('id')
+ .custom(isIdValid).withMessage('Should have correct import id'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking videoImportCancelValidator parameters', { parameters: req.params })
+
+ if (areValidationErrors(req, res)) return
+
+ if (!await doesVideoImportExist(parseInt(req.params.id), res)) return
+ if (!checkUserCanManageImport(res.locals.oauth.token.user, res.locals.videoImport, res)) return
+
+ if (res.locals.videoImport.state !== VideoImportState.PENDING) {
+ return res.fail({
+ status: HttpStatusCode.CONFLICT_409,
+ message: 'Cannot cancel a non pending video import.'
+ })
+ }
+
+ return next()
+ }
+]
+
// ---------------------------------------------------------------------------
export {
- videoImportAddValidator
+ videoImportAddValidator,
+ videoImportCancelValidator,
+ videoImportDeleteValidator
}
// ---------------------------------------------------------------------------
return true
}
+
+function checkUserCanManageImport (user: MUserAccountId, videoImport: MVideoImport, res: express.Response) {
+ if (user.hasRight(UserRight.MANAGE_VIDEO_IMPORTS) === false && videoImport.userId !== user.id) {
+ res.fail({
+ status: HttpStatusCode.FORBIDDEN_403,
+ message: 'Cannot manage video import of another user'
+ })
+ return false
+ }
+
+ return true
+}
import 'mocha'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
import { HttpStatusCode } from '@shared/models'
-import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
+import {
+ cleanupTests,
+ createSingleServer,
+ makeGetRequest,
+ makePostBodyRequest,
+ PeerTubeServer,
+ setAccessTokensToServers
+} from '@shared/server-commands'
describe('Test jobs API validators', function () {
const path = '/api/v1/jobs/failed'
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
+ })
+
+ describe('When pausing/resuming the job queue', async function () {
+ const commands = [ 'pause', 'resume' ]
+
+ it('Should fail with a non authenticated user', async function () {
+ for (const command of commands) {
+ await makePostBodyRequest({
+ url: server.url,
+ path: '/api/v1/jobs/' + command,
+ expectedStatus: HttpStatusCode.UNAUTHORIZED_401
+ })
+ }
+ })
+ it('Should fail with a non admin user', async function () {
+ for (const command of commands) {
+ await makePostBodyRequest({
+ url: server.url,
+ path: '/api/v1/jobs/' + command,
+ expectedStatus: HttpStatusCode.UNAUTHORIZED_401
+ })
+ }
+ })
+
+ it('Should succeed with the correct params', async function () {
+ for (const command of commands) {
+ await makePostBodyRequest({
+ url: server.url,
+ path: '/api/v1/jobs/' + command,
+ token: server.accessToken,
+ expectedStatus: HttpStatusCode.NO_CONTENT_204
+ })
+ }
+ })
})
after(async function () {
makePostBodyRequest,
makeUploadRequest,
PeerTubeServer,
- setAccessTokensToServers
+ setAccessTokensToServers,
+ setDefaultVideoChannel,
+ waitJobs
} from '@shared/server-commands'
describe('Test video imports API validator', function () {
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
+ await setDefaultVideoChannel([ server ])
const username = 'user1'
const password = 'my super password'
})
})
+ describe('Deleting/cancelling a video import', function () {
+ let importId: number
+
+ async function importVideo () {
+ const attributes = { channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
+ const res = await server.imports.importVideo({ attributes })
+
+ return res.id
+ }
+
+ before(async function () {
+ importId = await importVideo()
+ })
+
+ it('Should fail with an invalid import id', async function () {
+ await server.imports.cancel({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
+ await server.imports.delete({ importId: 'artyom' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
+ })
+
+ it('Should fail with an unknown import id', async function () {
+ await server.imports.cancel({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
+ await server.imports.delete({ importId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
+ })
+
+ it('Should fail without token', async function () {
+ await server.imports.cancel({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
+ await server.imports.delete({ importId, token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
+ })
+
+ it('Should fail with another user token', async function () {
+ await server.imports.cancel({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+ await server.imports.delete({ importId, token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+ })
+
+ it('Should fail to cancel non pending import', async function () {
+ this.timeout(60000)
+
+ await waitJobs([ server ])
+
+ await server.imports.cancel({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
+ })
+
+ it('Should succeed to delete an import', async function () {
+ await server.imports.delete({ importId })
+ })
+
+ it('Should fail to delete a pending import', async function () {
+ await server.jobs.pauseJobQueue()
+
+ importId = await importVideo()
+
+ await server.imports.delete({ importId, expectedStatus: HttpStatusCode.CONFLICT_409 })
+ })
+
+ it('Should succeed to cancel an import', async function () {
+ importId = await importVideo()
+
+ await server.imports.cancel({ importId })
+ })
+ })
+
after(async function () {
await cleanupTests([ server ])
})
setAccessTokensToServers,
waitJobs
} from '@shared/server-commands'
+import { wait } from '@shared/core-utils'
const expect = chai.expect
expect(jobs.find(j => j.state === 'completed')).to.not.be.undefined
})
+ it('Should pause the job queue', async function () {
+ this.timeout(120000)
+
+ await servers[1].jobs.pauseJobQueue()
+
+ await servers[1].videos.upload({ attributes: { name: 'video2' } })
+
+ await wait(5000)
+
+ const body = await servers[1].jobs.list({ state: 'waiting', jobType: 'video-transcoding' })
+ expect(body.data).to.have.lengthOf(1)
+ })
+
+ it('Should resume the job queue', async function () {
+ this.timeout(120000)
+
+ await servers[1].jobs.resumeJobQueue()
+
+ await waitJobs(servers)
+
+ const body = await servers[1].jobs.list({ state: 'waiting', jobType: 'video-transcoding' })
+ expect(body.data).to.have.lengthOf(0)
+ })
+
after(async function () {
await cleanupTests(servers)
})
import { join } from 'path'
import { FIXTURE_URLS, testCaptionFile, testImage } from '@server/tests/shared'
import { areHttpImportTestsDisabled } from '@shared/core-utils'
-import { VideoPrivacy, VideoResolution } from '@shared/models'
+import { HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models'
import {
cleanupTests,
createMultipleServers,
runSuite('yt-dlp')
+ describe('Delete/cancel an import', function () {
+ let server: PeerTubeServer
+
+ let finishedImportId: number
+ let finishedVideo: Video
+ let pendingImportId: number
+
+ async function importVideo (name: string) {
+ const attributes = { name, channelId: server.store.channel.id, targetUrl: FIXTURE_URLS.goodVideo }
+ const res = await server.imports.importVideo({ attributes })
+
+ return res.id
+ }
+
+ before(async function () {
+ this.timeout(120_000)
+
+ server = await createSingleServer(1)
+
+ await setAccessTokensToServers([ server ])
+ await setDefaultVideoChannel([ server ])
+
+ finishedImportId = await importVideo('finished')
+ await waitJobs([ server ])
+
+ await server.jobs.pauseJobQueue()
+ pendingImportId = await importVideo('pending')
+
+ const { data } = await server.imports.getMyVideoImports()
+ expect(data).to.have.lengthOf(2)
+
+ finishedVideo = data.find(i => i.id === finishedImportId).video
+ })
+
+ it('Should delete a video import', async function () {
+ await server.imports.delete({ importId: finishedImportId })
+
+ const { data } = await server.imports.getMyVideoImports()
+ expect(data).to.have.lengthOf(1)
+ expect(data[0].id).to.equal(pendingImportId)
+ expect(data[0].state.id).to.equal(VideoImportState.PENDING)
+ })
+
+ it('Should not have deleted the associated video', async function () {
+ const video = await server.videos.get({ id: finishedVideo.id, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+ expect(video.name).to.equal('finished')
+ expect(video.state.id).to.equal(VideoState.PUBLISHED)
+ })
+
+ it('Should cancel a video import', async function () {
+ await server.imports.cancel({ importId: pendingImportId })
+
+ const { data } = await server.imports.getMyVideoImports()
+ expect(data).to.have.lengthOf(1)
+ expect(data[0].id).to.equal(pendingImportId)
+ expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
+ })
+
+ it('Should not have processed the cancelled video import', async function () {
+ this.timeout(60_000)
+
+ await server.jobs.resumeJobQueue()
+
+ await waitJobs([ server ])
+
+ const { data } = await server.imports.getMyVideoImports()
+ expect(data).to.have.lengthOf(1)
+ expect(data[0].id).to.equal(pendingImportId)
+ expect(data[0].state.id).to.equal(VideoImportState.CANCELLED)
+ expect(data[0].video.state.id).to.equal(VideoState.TO_IMPORT)
+ })
+
+ it('Should delete the cancelled video import', async function () {
+ await server.imports.delete({ importId: pendingImportId })
+ const { data } = await server.imports.getMyVideoImports()
+ expect(data).to.have.lengthOf(0)
+ })
+ })
+
describe('Auto update', function () {
let server: PeerTubeServer
MANAGE_VIDEOS_REDUNDANCIES,
MANAGE_VIDEO_FILES,
- RUN_VIDEO_TRANSCODING
+ RUN_VIDEO_TRANSCODING,
+
+ MANAGE_VIDEO_IMPORTS
}
PENDING = 1,
SUCCESS = 2,
FAILED = 3,
- REJECTED = 4
+ REJECTED = 4,
+ CANCELLED = 5,
+ PROCESSING = 6
}
return data[0]
}
+ pauseJobQueue (options: OverrideCommandOptions = {}) {
+ const path = '/api/v1/jobs/pause'
+
+ return this.postBodyRequest({
+ ...options,
+
+ path,
+ implicitToken: true,
+ defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+ })
+ }
+
+ resumeJobQueue (options: OverrideCommandOptions = {}) {
+ const path = '/api/v1/jobs/resume'
+
+ return this.postBodyRequest({
+ ...options,
+
+ path,
+ implicitToken: true,
+ defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+ })
+ }
+
list (options: OverrideCommandOptions & {
state?: JobState
jobType?: JobType
}))
}
+ delete (options: OverrideCommandOptions & {
+ importId: number
+ }) {
+ const path = '/api/v1/videos/imports/' + options.importId
+
+ return this.deleteRequest({
+ ...options,
+
+ path,
+ implicitToken: true,
+ defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+ })
+ }
+
+ cancel (options: OverrideCommandOptions & {
+ importId: number
+ }) {
+ const path = '/api/v1/videos/imports/' + options.importId + '/cancel'
+
+ return this.postBodyRequest({
+ ...options,
+
+ path,
+ implicitToken: true,
+ defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
+ })
+ }
+
getMyVideoImports (options: OverrideCommandOptions & {
sort?: string
} = {}) {
The import function is practical when the desired video/audio is available online. It makes PeerTube
download it for you, saving you as much bandwidth and avoiding any instability or limitation your network might have.
+ - name: Video Imports
+ description: Operations dealing with listing, adding and removing video imports.
- name: Video Captions
description: Operations dealing with listing, adding and removing closed captions of a video.
- name: Video Channels
tags:
- Video
- Video Upload
+ - Video Imports
- Video Captions
- Video Channels
- Video Comments
'204':
description: successful operation
+ /jobs/pause:
+ post:
+ summary: Pause job queue
+ security:
+ - OAuth2:
+ - admin
+ tags:
+ - Job
+ responses:
+ '204':
+ description: successful operation
+
+ /jobs/resume:
+ post:
+ summary: Resume job queue
+ security:
+ - OAuth2:
+ - admin
+ tags:
+ - Job
+ responses:
+ '204':
+ description: successful operation
+
/jobs/{state}:
get:
summary: List instance jobs
security:
- OAuth2: []
tags:
- - Video
+ - Video Imports
- Video Upload
requestBody:
content:
'409':
description: HTTP or Torrent/magnetURI import not enabled
+ /videos/imports/{id}/cancel:
+ post:
+ summary: Cancel video import
+ description: Cancel a pending video import
+ security:
+ - OAuth2: []
+ tags:
+ - Video Imports
+ parameters:
+ - $ref: '#/components/parameters/id'
+ responses:
+ '204':
+ description: successful operation
+
+ /videos/imports/{id}:
+ delete:
+ summary: Delete video import
+ description: Delete ended video import
+ security:
+ - OAuth2: []
+ tags:
+ - Video Imports
+ parameters:
+ - $ref: '#/components/parameters/id'
+ responses:
+ '204':
+ description: successful operation
+
/videos/live:
post:
summary: Create a live
name: id
in: path
required: true
- description: The user id
+ description: Entity id
schema:
$ref: '#/components/schemas/id'
idOrUUID: