X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Factivitypub%2Fplaylist.ts;h=c2e2a3283fe5e9fc16d0f73b3b616bec5a881ea4;hb=5224c394b3bbac6ec1543e41fa0ec6db436e84fa;hp=c9b428c92c11083dffd0b6b1519d6340d68e3195;hpb=418d092afa81e2c8fe8ac6838fc4b5eb0af6a782;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index c9b428c92..c2e2a3283 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts @@ -1,12 +1,12 @@ import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' import { crawlCollectionPage } from './crawl' -import { ACTIVITY_PUB, CONFIG, CRAWL_REQUEST_CONCURRENCY, sequelizeTypescript, THUMBNAILS_SIZE } from '../../initializers' +import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' import { AccountModel } from '../../models/account/account' import { isArray } from '../../helpers/custom-validators/misc' import { getOrCreateActorAndServerAndModel } from './actor' import { logger } from '../../helpers/logger' import { VideoPlaylistModel } from '../../models/video/video-playlist' -import { doRequest, downloadImage } from '../../helpers/requests' +import { doRequest } from '../../helpers/requests' import { checkUrlsSameHost } from '../../helpers/activitypub' import * as Bluebird from 'bluebird' import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' @@ -14,11 +14,13 @@ import { getOrCreateVideoAndAccountAndChannel } from './videos' import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' import { VideoModel } from '../../models/video/video' -import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' -import { ActivityIconObject } from '../../../shared/models/activitypub/objects' +import { sequelizeTypescript } from '../../initializers/database' +import { createPlaylistMiniatureFromUrl } from '../thumbnail' +import { FilteredModelAttributes } from '../../typings/sequelize' +import { AccountModelId } from '../../typings/models' -function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) { +function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED return { @@ -28,7 +30,9 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount url: playlistObject.id, uuid: playlistObject.uuid, ownerAccountId: byAccount.id, - videoChannelId: null + videoChannelId: null, + createdAt: new Date(playlistObject.published), + updatedAt: new Date(playlistObject.updated) } } @@ -71,7 +75,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) } -async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) { +async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { @@ -93,16 +97,52 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc return Promise.resolve() }) - // Empty playlists generally do not have a miniature, so skip it - if (accItems.length !== 0) { + const refreshedPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(playlist.id, null) + + if (playlistObject.icon) { try { - await generateThumbnailFromUrl(playlist, playlistObject.icon) + const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist) + await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined) } catch (err) { logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err }) } + } else if (refreshedPlaylist.hasThumbnail()) { + await refreshedPlaylist.Thumbnail.destroy() + refreshedPlaylist.Thumbnail = null } - return resetVideoPlaylistElements(accItems, playlist) + return resetVideoPlaylistElements(accItems, refreshedPlaylist) +} + +async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise { + if (!videoPlaylist.isOutdated()) return videoPlaylist + + try { + const { statusCode, playlistObject } = await fetchRemoteVideoPlaylist(videoPlaylist.url) + if (statusCode === 404) { + logger.info('Cannot refresh remote video playlist %s: it does not exist anymore. Deleting it.', videoPlaylist.url) + + await videoPlaylist.destroy() + return undefined + } + + if (playlistObject === undefined) { + logger.warn('Cannot refresh remote playlist %s: invalid body.', videoPlaylist.url) + + await videoPlaylist.setAsRefreshed() + return videoPlaylist + } + + const byAccount = videoPlaylist.OwnerAccount + await createOrUpdateVideoPlaylist(playlistObject, byAccount, playlistObject.to) + + return videoPlaylist + } catch (err) { + logger.warn('Cannot refresh video playlist %s.', videoPlaylist.url, { err }) + + await videoPlaylist.setAsRefreshed() + return videoPlaylist + } } // --------------------------------------------------------------------------- @@ -111,7 +151,8 @@ export { createAccountPlaylists, playlistObjectToDBAttributes, playlistElementObjectToDBAttributes, - createOrUpdateVideoPlaylist + createOrUpdateVideoPlaylist, + refreshVideoPlaylistIfNeeded } // --------------------------------------------------------------------------- @@ -155,8 +196,22 @@ async function resetVideoPlaylistElements (elementUrls: string[], playlist: Vide return undefined } -function generateThumbnailFromUrl (playlist: VideoPlaylistModel, icon: ActivityIconObject) { - const thumbnailName = playlist.getThumbnailName() +async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { + const options = { + uri: playlistUrl, + method: 'GET', + json: true, + activityPub: true + } + + logger.info('Fetching remote playlist %s.', playlistUrl) + + const { response, body } = await doRequest(options) + + if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { + logger.debug('Remote video playlist JSON is not valid.', { body }) + return { statusCode: response.statusCode, playlistObject: undefined } + } - return downloadImage(icon.url, CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName, THUMBNAILS_SIZE) + return { statusCode: response.statusCode, playlistObject: body } }