# Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
reject_too_many_announces: false
+history:
+ videos:
+ # If you want to limit users videos history
+ # -1 means there is no limitations
+ # Other values could be '6 months' or '30 days' etc (PeerTube will periodically delete old entries from database)
+ max_age: -1
+
cache:
previews:
size: 500 # Max number of previews you want to cache
# Reject peers that do a lot of announces (could improve privacy of TCP/UDP peers)
reject_too_many_announces: false
+history:
+ videos:
+ # If you want to limit users videos history
+ # -1 means there is no limitations
+ # Other values could be '6 months' or '30 days' etc (PeerTube will periodically delete old entries from database)
+ max_age: -1
###############################################################################
#
import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
+import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler'
import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
import { PeerTubeSocket } from './server/lib/peertube-socket'
import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
UpdateVideosScheduler.Instance.enable()
YoutubeDlUpdateScheduler.Instance.enable()
VideosRedundancyScheduler.Instance.enable()
+ RemoveOldHistoryScheduler.Instance.enable()
// Redis initialization
Redis.Instance.init()
const beforeDate = req.body.beforeDate || null
await sequelizeTypescript.transaction(t => {
- return UserVideoHistoryModel.removeHistoryBefore(user, beforeDate, t)
+ return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
})
// Do not send the delete to other instances, we delete OUR copy of this video abuse
month: 3600000 * 24 * 30
}
-export function parseDuration (duration: number | string): number {
+export function parseDurationToMs (duration: number | string): number {
if (typeof duration === 'number') return duration
if (typeof duration === 'string') {
import * as validator from 'validator'
import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
import {
- CONSTRAINTS_FIELDS, MIMETYPES,
+ CONSTRAINTS_FIELDS,
+ MIMETYPES,
VIDEO_CATEGORIES,
VIDEO_LICENCES,
VIDEO_PRIVACIES,
import { dirname, join } from 'path'
import { VideosRedundancy } from '../../shared/models'
// Do not use barrels, remain constants as independent as possible
-import { buildPath, parseBytes, parseDuration, root } from '../helpers/core-utils'
+import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils'
import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
import * as bytes from 'bytes'
},
REDUNDANCY: {
VIDEOS: {
- CHECK_INTERVAL: parseDuration(config.get<string>('redundancy.videos.check_interval')),
+ CHECK_INTERVAL: parseDurationToMs(config.get<string>('redundancy.videos.check_interval')),
STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies'))
}
},
PRIVATE: config.get<boolean>('tracker.private'),
REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
},
+ HISTORY: {
+ VIDEOS: {
+ MAX_AGE: parseDurationToMs(config.get('history.videos.max_age'))
+ }
+ },
ADMIN: {
get EMAIL () { return config.get<string>('admin.email') }
},
return objs.map(obj => {
return Object.assign({}, obj, {
- minLifetime: parseDuration(obj.min_lifetime),
+ minLifetime: parseDurationToMs(obj.min_lifetime),
size: bytes.parse(obj.size),
minViews: obj.min_views
})
const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days
const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour
-// 1 hour
-let SCHEDULER_INTERVALS_MS = {
+const SCHEDULER_INTERVALS_MS = {
actorFollowScores: 60000 * 60, // 1 hour
removeOldJobs: 60000 * 60, // 1 hour
updateVideos: 60000, // 1 minute
- youtubeDLUpdate: 60000 * 60 * 24 // 1 day
+ youtubeDLUpdate: 60000 * 60 * 24, // 1 day
+ removeOldHistory: 60000 * 60 * 24 // 1 day
}
// ---------------------------------------------------------------------------
SCHEDULER_INTERVALS_MS.actorFollowScores = 1000
SCHEDULER_INTERVALS_MS.removeOldJobs = 10000
+ SCHEDULER_INTERVALS_MS.removeOldHistory = 5000
SCHEDULER_INTERVALS_MS.updateVideos = 5000
REPEAT_JOBS[ 'videos-views' ] = { every: 5000 }
}
function loadLanguages () {
- VIDEO_LANGUAGES = buildLanguages()
+ Object.assign(VIDEO_LANGUAGES, buildLanguages())
}
function buildLanguages () {
import { logger } from '../../helpers/logger'
+import * as Bluebird from 'bluebird'
export abstract class AbstractScheduler {
}
}
- protected abstract internalExecute (): Promise<any>
+ protected abstract internalExecute (): Promise<any> | Bluebird<any>
}
--- /dev/null
+import { logger } from '../../helpers/logger'
+import { AbstractScheduler } from './abstract-scheduler'
+import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
+import { UserVideoHistoryModel } from '../../models/account/user-video-history'
+import { CONFIG } from '../../initializers/config'
+import { isTestInstance } from '../../helpers/core-utils'
+
+export class RemoveOldHistoryScheduler extends AbstractScheduler {
+
+ private static instance: AbstractScheduler
+
+ protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.removeOldHistory
+
+ private constructor () {
+ super()
+ }
+
+ protected internalExecute () {
+ if (CONFIG.HISTORY.VIDEOS.MAX_AGE === -1) return
+
+ logger.info('Removing old videos history.')
+
+ const now = new Date()
+ const beforeDate = new Date(now.getTime() - CONFIG.HISTORY.VIDEOS.MAX_AGE).toISOString()
+
+ return UserVideoHistoryModel.removeOldHistory(beforeDate)
+ }
+
+ static get Instance () {
+ return this.instance || (this.instance = new this())
+ }
+}
import * as express from 'express'
import * as AsyncLock from 'async-lock'
-import { parseDuration } from '../helpers/core-utils'
+import { parseDurationToMs } from '../helpers/core-utils'
import { Redis } from '../lib/redis'
import { logger } from '../helpers/logger'
res.send = (body) => {
if (res.statusCode >= 200 && res.statusCode < 400) {
const contentType = res.get('content-type')
- const lifetime = parseDuration(lifetimeArg)
+ const lifetime = parseDurationToMs(lifetimeArg)
Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode)
.then(() => done())
})
}
- static removeHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) {
+ static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) {
const query: DestroyOptions = {
where: {
userId: user.id
return UserVideoHistoryModel.destroy(query)
}
+
+ static removeOldHistory (beforeDate: string) {
+ const query: DestroyOptions = {
+ where: {
+ updatedAt: {
+ [Op.lt]: beforeDate
+ }
+ }
+ }
+
+ return UserVideoHistoryModel.destroy(query)
+ }
}
flushTests,
getVideosListWithToken,
getVideoWithToken,
- killallServers,
+ killallServers, reRunServer,
runServer,
searchVideoWithToken,
ServerInfo,
setAccessTokensToServers,
updateMyUser,
uploadVideo,
- userLogin
+ userLogin,
+ wait
} from '../../../../shared/utils'
import { Video, VideoDetails } from '../../../../shared/models/videos'
import { listMyVideosHistory, removeMyVideosHistory, userWatchVideo } from '../../../../shared/utils/videos/video-history'
expect(videos[1].name).to.equal('video 3')
})
+ it('Should not clean old history', async function () {
+ this.timeout(50000)
+
+ killallServers([ server ])
+
+ await reRunServer(server, { history: { videos: { max_age: '10 days' } } })
+
+ await wait(6000)
+
+ // Should still have history
+
+ const res = await listMyVideosHistory(server.url, server.accessToken)
+
+ expect(res.body.total).to.equal(2)
+ })
+
+ it('Should clean old history', async function () {
+ this.timeout(50000)
+
+ killallServers([ server ])
+
+ await reRunServer(server, { history: { videos: { max_age: '5 seconds' } } })
+
+ await wait(6000)
+
+ const res = await listMyVideosHistory(server.url, server.accessToken)
+ expect(res.body.total).to.equal(0)
+ })
+
after(async function () {
killallServers([ server ])
} from '../'
import * as validator from 'validator'
import { VideoDetails, VideoPrivacy } from '../../models/videos'
-import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
+import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, loadLanguages, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
+loadLanguages()
+
type VideoAttributes = {
name?: string
category?: number