perf(faker): Improve torrent mocking (#1187)

This commit is contained in:
Rémi Marseault 2023-11-08 19:06:11 +01:00 committed by GitHub
parent 65b34ab685
commit 8b1f641fca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 137 additions and 153 deletions

8
__mocks__/torrents.json Normal file
View file

@ -0,0 +1,8 @@
[
{
"name": "First torrent"
},
{
"name": "Second torrent"
}
]

View file

@ -26,9 +26,6 @@ const paginationSizes = ref([
50
])
const isProduction = computed(() => process.env.NODE_ENV === 'production')
const isDevelopment = computed(() => process.env.NODE_ENV === 'development')
const theme = computed({
get() {
if (vueTorrentStore.matchSystemTheme) return 'auto'
@ -51,9 +48,9 @@ const themeOptions = [
]
const vueTorrentVersion = computed(() => {
if (isProduction.value) {
if (import.meta.env.PROD) {
return 'import.meta.env.VITE_PACKAGE_VERSION'
} else if (isDevelopment.value) {
} else if (import.meta.env.DEV) {
return 'DEV'
}

View file

@ -1,15 +1,24 @@
import { FilePriority, TorrentState } from '@/constants/qbit'
import { formatEta, getDomainBody } from '@/helpers'
import { Torrent } from '@/types/vuetorrent'
import { useMaindataStore } from '@/stores/maindata.ts'
import { Torrent as QbitTorrent } from '@/types/qbit/models'
import { Torrent } from '@/types/vuetorrent'
import { faker } from '@faker-js/faker'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
type StaticTorrent = Omit<Torrent, 'avgDownloadSpeed' | 'avgUploadSpeed' | 'globalSpeed' | 'globalVolume'>
export function useTorrentBuilder() {
const { t } = useI18n()
const maindataStore = useMaindataStore()
const categories = computed(() => maindataStore.categories.map(value => value.name) || ['ISO', 'Other', 'Movie', 'Music', 'TV'])
const computedValues = ['avgDownloadSpeed', 'avgUploadSpeed', 'globalSpeed', 'globalVolume', 'priority']
function buildFromQbit(data: QbitTorrent): Torrent {
const torrent = {
return buildTorrent({
added_on: data.added_on,
amount_left: data.amount_left,
auto_tmm: data.auto_tmm,
@ -47,7 +56,7 @@ export function useTorrentBuilder() {
seq_dl: data.seq_dl,
size: data.size,
state: data.state,
stateString: t(`torrent.state.${data.state}`),
stateString: t(`torrent.state.${ data.state }`),
super_seeding: data.super_seeding,
tags: data.tags.length > 0 ? data.tags.split(', ').map(t => t.trim()) : [],
time_active: data.time_active,
@ -59,21 +68,82 @@ export function useTorrentBuilder() {
uploaded: data.uploaded,
uploaded_session: data.uploaded_session,
upspeed: data.upspeed
}
const dlDuration = torrent.time_active - torrent.seeding_time
const ulDuration = torrent.time_active
return Object.freeze({
...torrent,
// const qlonglong dlDuration = torrent->activeTime() - torrent->finishedTime();
// dataDict[KEY_PROP_DL_SPEED_AVG] = torrent->totalDownload() / ((dlDuration == 0) ? -1 : dlDuration);
avgDownloadSpeed: torrent.downloaded / ((dlDuration == 0) ? -1 : dlDuration),
avgUploadSpeed: torrent.uploaded / ((ulDuration == 0) ? -1 : ulDuration),
globalSpeed: torrent.dlspeed + torrent.upspeed,
globalVolume: torrent.downloaded + torrent.uploaded
})
}
return { computedValues, buildFromQbit }
function buildFromFaker(data: Partial<Torrent>, index: number): Torrent {
const added_on = data.added_on || faker.date.past().getTime()
const available_peers = data.available_peers || faker.number.int({ min: 0, max: 250 })
const available_seeds = data.available_seeds || faker.number.int({ min: 0, max: 250 })
const state = data.state || faker.helpers.arrayElement(Object.values(TorrentState))
const total_size = data.total_size || faker.number.int({ min: 1000, max: 1_000_000_000_000 }) // [1 ko; 1 To]
const tracker = data.tracker || faker.internet.url()
return buildTorrent({
added_on,
amount_left: data.amount_left || faker.number.int({ min: 0, max: total_size }),
auto_tmm: data.auto_tmm || faker.datatype.boolean(),
availability: data.availability || faker.number.float({ min: 0, max: 100, precision: 0.01 }),
available_peers,
available_seeds,
category: data.category || faker.helpers.arrayElement(categories.value),
completed_on: data.completed_on || faker.date.between({ from: added_on, to: Date.now() }),
content_path: data.content_path || faker.system.filePath(),
dl_limit: data.dl_limit || faker.number.float({ min: 0, max: 1, precision: 0.01 }),
dlspeed: data.dlspeed || faker.number.int({ min: 0, max: 5000000 }),
download_path: data.download_path || faker.system.filePath(),
downloaded: data.downloaded || faker.number.float({ min: 0, max: 1, precision: 0.01 }),
downloaded_session: data.downloaded_session || faker.number.float({ min: 0, max: 1, precision: 0.01 }),
eta: data.eta || formatEta(faker.number.int({ min: 0, max: 900000 })),
forced: data.forced || faker.datatype.boolean(),
force_start: data.force_start || faker.datatype.boolean(),
hash: data.hash || faker.string.uuid(),
infohash_v1: data.infohash_v1 || faker.string.uuid(),
infohash_v2: data.infohash_v2 || faker.string.uuid(),
last_activity: data.last_activity || faker.number.int({ min: 0, max: 50 }),
magnet: data.magnet_uri || faker.internet.url(),
name: data.name || `Torrent ${ index + 1 }`,
num_leechs: data.num_leechs || faker.number.int(available_peers),
num_seeds: data.num_seeds || faker.number.int(available_seeds),
priority: data.priority || FilePriority.NORMAL,
progress: data.progress || faker.number.float({ min: 0, max: 1, precision: 0.01 }),
ratio: data.ratio || faker.number.float({ min: 0, max: 5, precision: 0.01 }),
ratio_limit: data.ratio_limit || faker.number.float({ min: 0, max: 4, precision: 0.01 }),
ratio_time_limit: data.ratio_time_limit || faker.number.float({ min: 0, max: 4, precision: 0.01 }),
savePath: data.savePath || faker.system.filePath(),
seeding_time: data.seeding_time || faker.number.int({ min: 0, max: 50 }),
seen_complete: data.seen_complete || faker.number.int({ min: 0, max: 50 }),
seq_dl: data.seq_dl || faker.datatype.boolean(),
size: data.size || faker.number.int({ min: 1000, max: total_size }),
state,
stateString: t(`torrent.state.${ state }`),
super_seeding: data.super_seeding || faker.datatype.boolean(),
tags: data.tags || '',
time_active: data.time_active || faker.number.int({ min: 1000, max: 900000 }),
total_size,
tracker,
tracker_domain: getDomainBody(tracker),
trackers_count: data.trackers_count || faker.number.int({ min: 1, max: 50 }),
up_limit: data.up_limit || faker.number.int({ min: 1000, max: 900000 }),
uploaded: data.uploaded || faker.number.int({ min: 1000, max: 900000 }),
uploaded_session: data.uploaded_session || faker.number.int({ min: 1000, max: 900000 }),
upspeed: data.upspeed || faker.number.int({ min: 0, max: 5000000 })
})
}
function buildTorrent(data: StaticTorrent): Torrent {
const dlDuration = data.time_active - data.seeding_time
const ulDuration = data.time_active
// @ts-expect-error: Type is missing the following properties from type 'Torrent': ...
return Object.freeze({
...data,
avgDownloadSpeed: data.downloaded / ((dlDuration == 0) ? -1 : dlDuration),
avgUploadSpeed: data.uploaded / ((ulDuration == 0) ? -1 : ulDuration),
globalSpeed: data.dlspeed + data.upspeed,
globalVolume: data.downloaded + data.uploaded
})
}
return { computedValues, buildFromQbit, buildFromFaker }
}

View file

@ -1,4 +1,13 @@
import { capitalize, codeToFlag, extractHostname, getDomainBody, splitByUrl, stringContainsUrl, titleCase } from './text'
import {
capitalize,
codeToFlag,
extractHostname,
getDomainBody,
splitByUrl,
stringContainsUrl,
titleCase,
uuidFromRaw
} from './text'
import { expect, test } from 'vitest'
test('helpers/text/titleCase', () => {
@ -68,3 +77,11 @@ test('helpers/text/codeToFlag', () => {
expect(codeToFlag('it').char).toBe('🇮🇹')
expect(codeToFlag('it').url).toBe('https://cdn.jsdelivr.net/npm/twemoji/2/svg/1f1ee-1f1f9.svg')
})
test('helpers/text/uuidFromRaw', () => {
expect(uuidFromRaw(0n)).toBe('00000000-0000-0000-0000-000000000000')
expect(uuidFromRaw(1n)).toBe('00000000-0000-0000-0000-000000000001')
expect(uuidFromRaw(0xAAAAAn)).toBe('00000000-0000-0000-0000-0000000aaaaa')
expect(uuidFromRaw(0x00000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff')
expect(uuidFromRaw(0xABCDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFn)).toBe('ffffffff-ffff-ffff-ffff-ffffffffffff')
})

View file

@ -83,3 +83,8 @@ export function codeToFlag(code: string) {
url
}
}
export function uuidFromRaw(bits: bigint) {
let bitString = bits.toString(16).slice(-32).padStart(32, "0");
return `${bitString.slice(-32, -24)}-${bitString.slice(-24, -20)}-${bitString.slice(-20, -16)}-${bitString.slice(-16, -12)}-${bitString.slice(-12)}`;
}

View file

@ -2,6 +2,7 @@ import { useTorrentBuilder } from '@/composables'
import { FilePriority, TorrentState } from '@/constants/qbit'
import { SortOptions } from '@/constants/qbit/SortOptions'
import { extractHostname } from '@/helpers'
import { uuidFromRaw } from '@/helpers/text'
import { qbit } from '@/services'
import { useAuthStore } from '@/stores/auth'
import { useDashboardStore } from '@/stores/dashboard'
@ -10,12 +11,9 @@ import { useVueTorrentStore } from '@/stores/vuetorrent'
import { Category, ServerState } from '@/types/qbit/models'
import { AddTorrentPayload } from '@/types/qbit/payloads'
import { Torrent } from '@/types/vuetorrent'
import { generateMultiple } from '@/utils/faker'
import { defineStore } from 'pinia'
import { computed, MaybeRefOrGetter, ref, toValue } from 'vue'
const isProduction = computed(() => process.env.NODE_ENV === 'production')
export const useMaindataStore = defineStore('maindata', () => {
const categories = ref<Category[]>([])
const isUpdatingMaindata = ref(false)
@ -181,9 +179,13 @@ export const useMaindataStore = defineStore('maindata', () => {
// update torrents
torrents.value = data.map(t => torrentBuilder.buildFromQbit(t))
if (!isProduction.value && import.meta.env.VITE_USE_FAKE_TORRENTS === 'true') {
const count = import.meta.env.VITE_FAKE_TORRENT_COUNT
torrents.value.push(...generateMultiple(count).map(t => torrentBuilder.buildFromQbit(t)))
if (import.meta.env.DEV && import.meta.env.VITE_USE_FAKE_TORRENTS === 'true') {
const count = Number(import.meta.env.VITE_FAKE_TORRENT_COUNT)
const fakeTorrents: Partial<Torrent> = (await import('../../__mocks__/torrents.json')).default
for (let i = 1; i <= count; i++) {
torrents.value.push(torrentBuilder.buildFromFaker({ ...fakeTorrents.at(i), hash: uuidFromRaw(BigInt(i)) }, i))
}
}
// filter out deleted torrents from selection

View file

@ -38,6 +38,7 @@ export default interface Torrent {
seq_dl: boolean
size: number
state: TorrentState
stateString: string
super_seeding: boolean
tags: string[]
time_active: number

View file

@ -1,121 +0,0 @@
import { faker } from '@faker-js/faker'
import type Torrent from '@/types/qbit/models/Torrent'
import { TorrentState } from '@/constants/qbit/TorrentState'
import { FilePriority } from '@/constants/qbit/FilePriority'
export function generateMultiple(count: number = 1): Torrent[] {
const torrents: Torrent[] = []
for (let i = 0; i < count; i++) {
torrents.push(
generateTorrent({
name: 'Torrent - ' + i
})
)
}
return torrents
}
export function generateTorrent(data: Partial<Torrent>): Torrent {
return {
/** Time (Unix Epoch) when the torrent was added to the client */
added_on: faker.date.recent().getTime(),
/** Amount of data left to download (bytes) */
amount_left: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Whether this torrent is managed by Automatic Torrent Management */
auto_tmm: faker.datatype.boolean(),
/** Percentage of file pieces currently available */
availability: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Category of the torrent */
category: faker.helpers.arrayElement(['ISO', 'Other', 'Movie', 'Music', 'TV']),
/** Amount of transfer data completed (bytes) */
completed: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Time (Unix Epoch) when the torrent completed */
completion_on: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Absolute path of torrent content (root path for multifile torrents, absolute file path for singlefile torrents) */
content_path: faker.system.filePath(),
/** Torrent download speed limit (bytes/s). -1 if unlimited. */
dl_limit: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Torrent download speed (bytes/s) */
dlspeed: faker.number.int({ min: 0, max: 5000000 }),
/** TODO */
download_path: faker.system.filePath(),
/** Amount of data downloaded */
downloaded: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Amount of data downloaded this session */
downloaded_session: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Torrent ETA (seconds) */
eta: faker.number.int({ min: 1000, max: 900000 }),
/** True if first last piece are prioritized */
f_l_piece_prio: faker.datatype.boolean(),
/** True if force start is enabled for this torrent */
force_start: faker.datatype.boolean(),
/** Torrent hash */
hash: faker.string.uuid(),
inactive_seeding_time_limit: faker.number.int({ min: 0, max: 50 }),
/** TODO */
infohash_v1: faker.string.uuid(),
/** TODO */
infohash_v2: faker.string.uuid(),
/** Last time (Unix Epoch) when a chunk was downloaded/uploaded */
last_activity: faker.number.int({ min: 0, max: 50 }),
/** Magnet URI corresponding to this torrent */
magnet_uri: faker.internet.url(),
max_inactive_seeding_time: faker.number.int({ min: 0, max: 50 }),
/** Maximum share ratio until torrent is stopped from seeding/uploading */
max_ratio: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Maximum seeding time (seconds) until torrent is stopped from seeding */
max_seeding_time: faker.number.int({ min: 0, max: 50 }),
/** Torrent name */
name: data.name || faker.animal.dog(),
/** Number of seeds in the swarm */
num_complete: faker.number.int({ min: 0, max: 50 }),
/** Number of leechers in the swarm */
num_incomplete: faker.number.int({ min: 0, max: 50 }),
/** Number of leechers connected to */
num_leechs: faker.number.int({ min: 0, max: 50 }),
/** Number of seeds connected to */
num_seeds: faker.number.int({ min: 0, max: 50 }),
/** Torrent priority. Returns -1 if queuing is disabled or torrent is in seed mode */
priority: FilePriority.NORMAL,
/** Torrent progress (percentage/100) */
progress: faker.number.float({ min: 0, max: 1, precision: 0.01 }),
/** Torrent share ratio. Max ratio value: 9999. */
ratio: faker.number.float({ min: 0, max: 5, precision: 0.01 }),
/** TODO */
ratio_limit: faker.number.float({ min: 0, max: 4, precision: 0.01 }),
/** Path where this torrent's data is stored */
save_path: faker.system.filePath(),
/** Torrent elapsed time while complete (seconds) */
seeding_time: faker.number.int({ min: 0, max: 50 }),
/** TODO */
seeding_time_limit: faker.number.int({ min: 0, max: 50 }),
/** Time (Unix Epoch) when this torrent was last seen complete */
seen_complete: faker.number.int({ min: 0, max: 50 }),
/** True if sequential download is enabled */
seq_dl: faker.datatype.boolean(),
/** Total size (bytes) of files selected for download */
size: faker.number.int({ min: 1000000, max: 50000000000 }),
/** Torrent state. See table here below for the possible values */
state: faker.helpers.arrayElement(Object.values(TorrentState)),
/** True if super seeding is enabled */
super_seeding: faker.datatype.boolean(),
/** Comma-concatenated tag list of the torrent */
tags: '',
/** Total active time (seconds) */
time_active: faker.number.int({ min: 1000, max: 900000 }),
/** Total size (bytes) of all file in this torrent (including unselected ones) */
total_size: faker.number.int({ min: 1000, max: 900000 }),
/** The first tracker with working status. Returns empty string if no tracker is working. */
tracker: faker.animal.cat(),
/** TODO */
trackers_count: faker.number.int({ min: 1000, max: 900000 }),
/** Torrent upload speed limit (bytes/s). -1 if unlimited. */
up_limit: faker.number.int({ min: 1000, max: 900000 }),
/** Amount of data uploaded */
uploaded: faker.number.int({ min: 1000, max: 900000 }),
/** Amount of data uploaded this session */
uploaded_session: faker.number.int({ min: 1000, max: 900000 }),
/** Torrent upload speed (bytes/s) */
upspeed: faker.number.int({ min: 0, max: 5000000 })
}
}

View file

@ -7,12 +7,12 @@ import { VitePWA } from 'vite-plugin-pwa'
import { resolve } from 'node:path'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd())
const qBittorrentPort = env.VITE_QBITTORRENT_PORT ?? '8080'
const proxyTarget = env.VITE_QBITTORRENT_TARGET ?? 'http://127.0.0.1'
const version = process.env.NODE_ENV === 'production' ? process.env.npm_package_version : JSON.stringify(process.env.npm_package_version)
const version = command === 'build' ? process.env.npm_package_version : JSON.stringify(process.env.npm_package_version)
return {
base: './',
@ -22,7 +22,12 @@ export default defineConfig(({ mode }) => {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'vue-router']
// vue stuff
vue: ['vue', 'vue-router', 'pinia', 'pinia-plugin-persist'],
// vuetify stuff
vuetify: ['vuetify'],
// faker stuff
faker: ['@faker-js/faker']
}
}
}