peertube-redundancy.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import bytes from 'bytes'
  2. import CliTable3 from 'cli-table3'
  3. import { URL } from 'url'
  4. import { Command } from '@commander-js/extra-typings'
  5. import { forceNumber, uniqify } from '@peertube/peertube-core-utils'
  6. import { HttpStatusCode, VideoRedundanciesTarget } from '@peertube/peertube-models'
  7. import { assignToken, buildServer, CommonProgramOptions, getServerCredentials } from './shared/index.js'
  8. export function defineRedundancyProgram () {
  9. const program = new Command()
  10. .name('redundancy')
  11. .description('Manage instance redundancies')
  12. .alias('r')
  13. program
  14. .command('list-remote-redundancies')
  15. .description('List remote redundancies on your videos')
  16. .option('-u, --url <url>', 'Server url')
  17. .option('-U, --username <username>', 'Username')
  18. .option('-p, --password <token>', 'Password')
  19. .action(async options => {
  20. try {
  21. await listRedundanciesCLI({ target: 'my-videos', ...options })
  22. } catch (err) {
  23. console.error('Cannot list remote redundancies: ' + err.message)
  24. process.exit(-1)
  25. }
  26. })
  27. program
  28. .command('list-my-redundancies')
  29. .description('List your redundancies of remote videos')
  30. .option('-u, --url <url>', 'Server url')
  31. .option('-U, --username <username>', 'Username')
  32. .option('-p, --password <token>', 'Password')
  33. .action(async options => {
  34. try {
  35. await listRedundanciesCLI({ target: 'remote-videos', ...options })
  36. } catch (err) {
  37. console.error('Cannot list redundancies: ' + err.message)
  38. process.exit(-1)
  39. }
  40. })
  41. program
  42. .command('add')
  43. .description('Duplicate a video in your redundancy system')
  44. .option('-u, --url <url>', 'Server url')
  45. .option('-U, --username <username>', 'Username')
  46. .option('-p, --password <token>', 'Password')
  47. .requiredOption('-v, --video <videoId>', 'Video id to duplicate', parseInt)
  48. .action(async options => {
  49. try {
  50. await addRedundancyCLI(options)
  51. } catch (err) {
  52. console.error('Cannot duplicate video: ' + err.message)
  53. process.exit(-1)
  54. }
  55. })
  56. program
  57. .command('remove')
  58. .description('Remove a video from your redundancies')
  59. .option('-u, --url <url>', 'Server url')
  60. .option('-U, --username <username>', 'Username')
  61. .option('-p, --password <token>', 'Password')
  62. .requiredOption('-v, --video <videoId>', 'Video id to remove from redundancies', parseInt)
  63. .action(async options => {
  64. try {
  65. await removeRedundancyCLI(options)
  66. } catch (err) {
  67. console.error('Cannot remove redundancy: ' + err)
  68. process.exit(-1)
  69. }
  70. })
  71. return program
  72. }
  73. // ----------------------------------------------------------------------------
  74. async function listRedundanciesCLI (options: CommonProgramOptions & { target: VideoRedundanciesTarget }) {
  75. const { target } = options
  76. const { url, username, password } = await getServerCredentials(options)
  77. const server = buildServer(url)
  78. await assignToken(server, username, password)
  79. const { data } = await server.redundancy.listVideos({ start: 0, count: 100, sort: 'name', target })
  80. const table = new CliTable3({
  81. head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ]
  82. }) as any
  83. for (const redundancy of data) {
  84. const webVideoFiles = redundancy.redundancies.files
  85. const streamingPlaylists = redundancy.redundancies.streamingPlaylists
  86. let totalSize = ''
  87. if (target === 'remote-videos') {
  88. const tmp = webVideoFiles.concat(streamingPlaylists)
  89. .reduce((a, b) => a + b.size, 0)
  90. // FIXME: don't use external dependency to stringify bytes: we already have the functions in the client
  91. totalSize = bytes(tmp)
  92. }
  93. const instances = uniqify(
  94. webVideoFiles.concat(streamingPlaylists)
  95. .map(r => r.fileUrl)
  96. .map(u => new URL(u).host)
  97. )
  98. table.push([
  99. redundancy.id.toString(),
  100. redundancy.name,
  101. redundancy.url,
  102. webVideoFiles.length,
  103. streamingPlaylists.length,
  104. instances.join('\n'),
  105. totalSize
  106. ])
  107. }
  108. console.log(table.toString())
  109. }
  110. async function addRedundancyCLI (options: { video: number } & CommonProgramOptions) {
  111. const { url, username, password } = await getServerCredentials(options)
  112. const server = buildServer(url)
  113. await assignToken(server, username, password)
  114. if (!options.video || isNaN(options.video)) {
  115. throw new Error('You need to specify the video id to duplicate and it should be a number.')
  116. }
  117. try {
  118. await server.redundancy.addVideo({ videoId: options.video })
  119. console.log('Video will be duplicated by your instance!')
  120. } catch (err) {
  121. if (err.message.includes(HttpStatusCode.CONFLICT_409)) {
  122. throw new Error('This video is already duplicated by your instance.')
  123. }
  124. if (err.message.includes(HttpStatusCode.NOT_FOUND_404)) {
  125. throw new Error('This video id does not exist.')
  126. }
  127. throw err
  128. }
  129. }
  130. async function removeRedundancyCLI (options: CommonProgramOptions & { video: number }) {
  131. const { url, username, password } = await getServerCredentials(options)
  132. const server = buildServer(url)
  133. await assignToken(server, username, password)
  134. if (!options.video || isNaN(options.video)) {
  135. throw new Error('You need to specify the video id to remove from your redundancies')
  136. }
  137. const videoId = forceNumber(options.video)
  138. const myVideoRedundancies = await server.redundancy.listVideos({ target: 'my-videos' })
  139. let videoRedundancy = myVideoRedundancies.data.find(r => videoId === r.id)
  140. if (!videoRedundancy) {
  141. const remoteVideoRedundancies = await server.redundancy.listVideos({ target: 'remote-videos' })
  142. videoRedundancy = remoteVideoRedundancies.data.find(r => videoId === r.id)
  143. }
  144. if (!videoRedundancy) {
  145. throw new Error('Video redundancy not found.')
  146. }
  147. const ids = videoRedundancy.redundancies.files
  148. .concat(videoRedundancy.redundancies.streamingPlaylists)
  149. .map(r => r.id)
  150. for (const id of ids) {
  151. await server.redundancy.removeVideo({ redundancyId: id })
  152. }
  153. console.log('Video redundancy removed!')
  154. }