perf: Add missing torrent properties to TorrentDetail > Info (#744)

This commit is contained in:
Rémi Marseault 2023-04-02 09:20:32 +02:00 committed by GitHub
parent a93cbb7892
commit ecd8a4e608
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 242 additions and 125 deletions

2
package-lock.json generated
View file

@ -6,7 +6,7 @@
"packages": {
"": {
"name": "vuetorrent",
"version": "1.5.1",
"version": "1.5.3",
"dependencies": {
"ajv": "^8.12.0",
"apexcharts": "^3.35.0",

View file

@ -2,7 +2,7 @@
<v-card flat>
<v-simple-table>
<tbody>
<tr>
<tr id="torrentName">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.torrentTitle') }}
</td>
@ -10,7 +10,18 @@
{{ torrent.name }}
</td>
</tr>
<tr>
<tr id="torrentComment" v-if="comment">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.comments') | titleCase }}
</td>
<td>
<span v-for="commentPart in splitString(comment)" :key="commentPart">
<a v-if="stringContainsUrl(commentPart)" target="_blank" :href="commentPart">{{ commentPart }}</a>
<span v-else>{{ commentPart }}</span>
</span>
</td>
</tr>
<tr id="torrentPieceState">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.pieceStates') }}
</td>
@ -19,15 +30,15 @@
<canvas width="0" height="1" />
</td>
</tr>
<tr>
<tr id="torrentSavePath">
<td :class="commonStyle">
{{ $t('torrent.directory') | titleCase }}
{{ $t('torrent.properties.save_path') | titleCase }}
</td>
<td>
{{ torrent.savePath }}
</td>
</tr>
<tr style="margin-top: 10px !important">
<tr id="torrentHash">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.hash') }}
</td>
@ -35,43 +46,52 @@
{{ torrent.hash }}
</td>
</tr>
<tr>
<tr id="torrentSize">
<td :class="commonStyle">
{{ $t('torrent.size') | titleCase }}
{{ $t('torrent.properties.size') | titleCase }}
</td>
<td>
{{ torrent.size | getDataValue }}
{{ torrent.size | getDataUnit(1) }}
{{ torrent.size | getDataUnit }}
</td>
</tr>
<tr>
<tr id="torrentTotalSize">
<td :class="commonStyle">
{{ $t('torrent.timeActive') | titleCase }}
{{ $t('torrent.properties.total_size') | titleCase }}
</td>
<td>
{{ torrent.total_size | getDataValue }}
{{ torrent.total_size | getDataUnit }}
</td>
</tr>
<tr id="torrentTimeActive">
<td :class="commonStyle">
{{ $t('torrent.properties.time_active') | titleCase }}
</td>
<td>
{{ torrent.time_active }}
<span v-if="torrent.seeding_time">{{ `(${$t('torrent.seededFor')} ${torrent.seeding_time})` }}</span>
<span>{{ seedingTime }}</span>
</td>
</tr>
<tr>
<tr id="torrentDownloaded">
<td :class="commonStyle">
{{ $t('torrent.downloaded') | titleCase }}
</td>
<td>
{{ torrent.downloaded | getDataValue }}
{{ torrent.downloaded | getDataUnit(1) }}
{{ torrent.downloaded | getDataUnit }}
</td>
</tr>
<tr>
<tr id="torrentUploaded">
<td :class="commonStyle">
{{ $t('torrent.uploaded') | titleCase }}
</td>
<td>
{{ torrent.uploaded | getDataValue }}
{{ torrent.uploaded | getDataUnit(1) }}
{{ torrent.uploaded | getDataUnit }}
</td>
</tr>
<tr>
<tr id="torrentRatio">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.ratio') }}
</td>
@ -79,25 +99,43 @@
{{ torrent.ratio }}
</td>
</tr>
<tr>
<tr id="torrentDlSpeed">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.downloadSpeed') }}
</td>
<td>
{{ torrent.dlspeed | getDataValue }}
{{ torrent.dlspeed | getDataUnit(1) }}/s
{{ torrent.dlspeed | getDataUnit }}/s
</td>
</tr>
<tr>
<tr id="torrentDlSpeedAvg">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.dl_speed_average') | titleCase }}
</td>
<td>
{{ downloadSpeedAvg | getDataValue }}
{{ downloadSpeedAvg | getDataUnit }}/s
</td>
</tr>
<tr id="torrentUpSpeed">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.uploadSpeed') }}
</td>
<td>
{{ torrent.upspeed | getDataValue }}
{{ torrent.upspeed | getDataUnit(1) }}/s
{{ torrent.upspeed | getDataUnit }}/s
</td>
</tr>
<tr>
<tr id="torrentUpSpeedAvg">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.up_speed_average') | titleCase }}
</td>
<td>
{{ uploadSpeedAvg | getDataValue }}
{{ uploadSpeedAvg | getDataUnit }}/s
</td>
</tr>
<tr id="torrentEta">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.eta') }}
</td>
@ -105,7 +143,7 @@
{{ torrent.eta }}
</td>
</tr>
<tr>
<tr id="torrentPeers">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.peers') }}
</td>
@ -113,7 +151,7 @@
{{ torrent.num_leechs }}<span :class="commonStyle">/{{ torrent.available_peers }}</span>
</td>
</tr>
<tr>
<tr id="torrentSeeds">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.seeds') }}
</td>
@ -121,25 +159,33 @@
{{ torrent.num_seeds }}<span :class="commonStyle">/{{ torrent.available_seeds }}</span>
</td>
</tr>
<tr>
<tr id="torrentAddedOn">
<td :class="commonStyle">
{{ $t('torrent.added') | titleCase }}
{{ $t('torrent.properties.added_on') | titleCase }}
</td>
<td>
{{ torrent.added_on }}
</td>
</tr>
<tr>
<tr id="torrentCreationDate">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.creation_date') | titleCase }}
</td>
<td>
{{ creationDate }}
</td>
</tr>
<tr id="torrentState">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.status') }}
</td>
<td>
<v-chip small :class="`${torrent.state.toLowerCase()} white--text caption`">
<v-chip small :class="`${torrentStateClass} white--text caption`">
{{ torrent.state }}
</v-chip>
</td>
</tr>
<tr v-if="torrent.tracker">
<tr id="torrentTrackers" v-if="torrent?.tracker">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.trackers') }}
</td>
@ -150,7 +196,7 @@
</span>
</td>
</tr>
<tr v-if="createdBy">
<tr id="torrentCreatedBy" v-if="createdBy">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.createdBy') }}
</td>
@ -161,19 +207,8 @@
</span>
</td>
</tr>
<tr v-if="comment">
<td :class="commonStyle">
{{ $t('torrent.comments') | titleCase }}
</td>
<td>
<span v-for="commentPart in splitString(comment)" :key="commentPart">
<a v-if="stringContainsUrl(commentPart)" target="_blank" :href="commentPart">{{ commentPart }}</a>
<span v-else>{{ commentPart }}</span>
</span>
</td>
</tr>
<tr>
<tr id="torrentFLPiecePrio">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.firstLastPiecePriority') }}
</td>
@ -181,7 +216,7 @@
{{ torrent.f_l_piece_prio }}
</td>
</tr>
<tr>
<tr id="torrentSeqDl">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.sequentialDownload') }}
</td>
@ -189,7 +224,7 @@
{{ torrent.seq_dl }}
</td>
</tr>
<tr>
<tr id="torrentAutoTMM">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.autoTMM') }}
</td>
@ -197,7 +232,7 @@
{{ torrent.auto_tmm }}
</td>
</tr>
<tr>
<tr id="torrentRatioLimit">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.shareRatioLimit') }}
</td>
@ -205,7 +240,7 @@
{{ torrent.ratio_limit | limitToValue }}
</td>
</tr>
<tr>
<tr id="torrentRatioTimeLimit">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.shareTimeLimit') }}
</td>
@ -213,127 +248,200 @@
{{ torrent.ratio_time_limit | limitToValue }}
</td>
</tr>
<tr>
<tr id="torrentDlLimit">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.downloadLimit') }}
</td>
<td v-if="torrent.dl_limit > 0">{{ torrent.dl_limit | getDataValue }} {{ torrent.dl_limit | getDataUnit }}<span>/s </span></td>
<td v-if="torrent?.dl_limit > 0">
{{ torrent.dl_limit | getDataValue }}
{{ torrent.dl_limit | getDataUnit }}<span>/s </span>
</td>
<td v-else></td>
</tr>
<tr>
<tr id="torrentUpLimit">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.uploadLimit') }}
</td>
<td v-if="torrent.up_limit > 0">{{ torrent.up_limit | getDataValue }} {{ torrent.up_limit | getDataUnit }}<span>/s </span></td>
<td v-if="torrent?.up_limit > 0">
{{ torrent.up_limit | getDataValue }}
{{ torrent.up_limit | getDataUnit }}<span>/s </span>
</td>
<td v-else></td>
</tr>
<tr>
<tr id="torrentAvailability">
<td :class="commonStyle">
{{ $t('torrent.availability') | titleCase }}
{{ $t('torrent.properties.availability') | titleCase }}
</td>
<td>
{{ torrent.availability }}
</td>
</tr>
<tr id="torrentPrivate">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.is_private') | titleCase }}
</td>
<td>
{{ isPrivateTorrent }}
</td>
</tr>
<tr id="torrentPieceCount">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.piece_owned') | titleCase }}
</td>
<td>{{ torrentPieceOwned }} / {{ torrentPieceCount }}</td>
</tr>
<tr id="torrentPieceSize">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.piece_size') | titleCase }}
</td>
<td>
{{ torrentPieceSize | getDataValue }}
{{ torrentPieceSize | getDataUnit }}
</td>
</tr>
<tr id="torrentWastedSize">
<td :class="commonStyle">
{{ $t('modals.detail.pageInfo.wasted_size') | titleCase }}
</td>
<td>
{{ wastedSize | getDataValue }}
{{ wastedSize | getDataUnit }}
</td>
</tr>
</tbody>
</v-simple-table>
</v-card>
</template>
<script>
<script lang="ts">
import dayjs from 'dayjs'
import { FullScreenModal } from '@/mixins'
import qbit from '@/services/qbit'
import { splitByUrl, stringContainsUrl } from '@/helpers'
import { defineComponent } from 'vue'
import { Torrent } from '@/models'
import { mapState } from 'vuex'
export default {
export default defineComponent({
name: 'Info',
mixins: [FullScreenModal],
props: {
hash: String,
torrent: Object
torrent: Torrent,
isActive: Boolean
},
data() {
return {
commonStyle: 'caption',
createdBy: null,
comment: null
comment: '',
createdBy: '',
creationDate: '',
downloadSpeedAvg: 0,
isPrivateTorrent: false,
torrentPieceSize: 0,
torrentPieceOwned: 0,
torrentPieceCount: 0,
wastedSize: 0,
uploadSpeedAvg: 0
}
},
mounted() {
this.getTorrentProperties()
this.renderTorrentPieceStates()
async mounted() {
await this.getTorrentProperties()
await this.renderTorrentPieceStates()
},
computed: {
...mapState(['webuiSettings']),
seedingTime() {
if (!this.torrent?.seeding_time) return ''
const content = this.$t('modals.detail.pageInfo.seededFor').toString().replace('$0', this.torrent.seeding_time)
return `(${content})`
},
torrentStateClass() {
return this.torrent?.state ? this.torrent.state.toLowerCase() : ''
}
},
methods: {
async getTorrentProperties() {
const props = await qbit.getTorrentProperties(this.hash)
this.createdBy = props.created_by || null
this.comment = props.comment || null
const props = await qbit.getTorrentProperties(this.hash as string)
this.comment = props.comment
this.createdBy = props.created_by
// @ts-expect-error: TS2339: Property 'dateFormat' does not exist on type 'never'.
this.creationDate = dayjs(props.creation_date * 1000).format(this.webuiSettings.dateFormat)
this.downloadSpeedAvg = props.dl_speed_avg
this.isPrivateTorrent = props.is_private
this.torrentPieceSize = props.piece_size
this.torrentPieceOwned = props.pieces_have
this.torrentPieceCount = props.pieces_num
this.wastedSize = props.total_wasted
this.uploadSpeedAvg = props.up_speed_avg
},
async renderTorrentPieceStates() {
const canvas = document.querySelector('#pieceStates canvas')
const canvas: HTMLCanvasElement | null = document.querySelector('#pieceStates canvas')
if (canvas === null) return
const files = await qbit.getTorrentFiles(this.hash)
const pieces = await qbit.getTorrentPieceStates(this.hash)
const files = await qbit.getTorrentFiles(this.hash as string)
const pieces = await qbit.getTorrentPieceStates(this.hash as string)
if (!pieces) return
// Source: https://github.com/qbittorrent/qBittorrent/blob/6229b817300344759139d2fedbd59651065a561d/src/webui/www/private/scripts/prop-general.js#L230
if (pieces) {
canvas.width = pieces.length
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
canvas.width = pieces.length
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Group contiguous colors together and draw as a single rectangle
let color = ''
let rectWidth = 1
// Group contiguous colors together and draw as a single rectangle
let color = ''
let rectWidth = 1
for (let i = 0; i < pieces.length; ++i) {
const status = pieces[i]
let newColor = ''
for (let i = 0; i < pieces.length; ++i) {
const status = pieces[i]
let newColor = ''
if (status === 1)
// requested / downloading
newColor = this.$vuetify.theme.currentTheme['torrent-downloading']
else if (status === 2)
// already downloaded
newColor = this.$vuetify.theme.currentTheme['torrent-done']
else {
const selected_piece_ranges = files.filter(file => file.priority !== 0).map(file => file.piece_range)
for (const [min_piece_range, max_piece_range] of selected_piece_ranges) {
if (i > min_piece_range && i < max_piece_range) {
newColor = this.$vuetify.theme.currentTheme['torrent-paused']
break
}
if (status === 1)
// requested / downloading
newColor = this.$vuetify.theme.currentTheme['torrent-downloading'] as string
else if (status === 2)
// already downloaded
newColor = this.$vuetify.theme.currentTheme['torrent-done'] as string
else {
// pending download
const selected_piece_ranges = files.filter(file => file.priority !== 0).map(file => file.piece_range)
for (const [min_piece_range, max_piece_range] of selected_piece_ranges) {
if (i > min_piece_range && i < max_piece_range) {
newColor = this.$vuetify.theme.currentTheme['torrent-paused'] as string
break
}
}
if (newColor === color) {
++rectWidth
continue
}
if (color !== '') {
ctx.fillStyle = color
ctx.fillRect(i - rectWidth, 0, rectWidth, canvas.height)
}
rectWidth = 1
color = newColor
}
// Fill a rect at the end of the canvas if one is needed
if (newColor === color) {
++rectWidth
continue
}
if (color !== '') {
ctx.fillStyle = color
ctx.fillRect(pieces.length - rectWidth, 0, rectWidth, canvas.height)
ctx.fillRect(i - rectWidth, 0, rectWidth, canvas.height)
}
rectWidth = 1
color = newColor
}
// Fill a rect at the end of the canvas if one is needed
if (color !== '') {
ctx.fillStyle = color
ctx.fillRect(pieces.length - rectWidth, 0, rectWidth, canvas.height)
}
},
stringContainsUrl(string) {
stringContainsUrl(string: string) {
return stringContainsUrl(string)
},
splitString(string) {
splitString(string: string) {
return splitByUrl(string)
}
}
}
})
</script>
<style lang="scss" scoped>
@ -345,6 +453,7 @@ export default {
&:first-child {
padding: 0 0 0 8px !important;
}
&:last-child {
padding-right: 8px !important;
}
@ -359,7 +468,7 @@ export default {
canvas {
height: 100%;
width: 50%;
width: 75%;
border: 1px dotted;
}
}

View file

@ -20,13 +20,7 @@ export function formatSize(value: number): string {
index++
}
const unit = index < 0 ? 'B' : `${units[index]}iB`
if (index < 0) {
return `${value} ${unit}`
}
return `${toPrecision(value, 3)} ${unit}`
return index < 0 ? `${value} B` : `${toPrecision(value, 3)} ${units[index]}iB`
}
Vue.filter('formatSize', formatSize)

View file

@ -101,13 +101,8 @@
"uploaded": "Uploaded (global)",
"uploaded_session": "Uploaded (session)"
},
"directory": "Directory",
"size": "Size",
"timeActive": "Time Active",
"downloaded": "Downloaded",
"uploaded": "Uploaded",
"added": "Added On",
"availability": "Availability"
"uploaded": "Uploaded"
},
"navbar": {
"currentSpeed": "Current Speed",
@ -413,14 +408,23 @@
"seeds": "Seeds",
"status": "Status",
"trackers": "Trackers",
"comments": "Comments",
"createdBy": "Created By",
"seededFor": "Seeded For $0",
"firstLastPiecePriority": "First/Last Piece Priority",
"sequentialDownload": "Sequential Download",
"autoTMM": "Automatic Torrent Management",
"shareRatioLimit": "Share Ratio Limit",
"shareTimeLimit": "Share Time Limit (minutes)",
"downloadLimit": "Download Limit",
"uploadLimit": "Upload Limit"
"uploadLimit": "Upload Limit",
"creation_date": "Creation Date",
"dl_speed_average": "Download Speed Average",
"up_speed_average": "Upload Speed Average",
"is_private": "Torrent Private",
"piece_owned": "Collected pieces",
"piece_size": "Piece Size",
"wasted_size": "Wasted"
},
"pagePeers": {
"ip": "IP",

View file

@ -15,10 +15,22 @@ export default interface TorrentProperties {
dl_speed: number
/** Torrent average download speed (bytes/second) */
dl_speed_avg: number
/** Torrent download path */
download_path: string
/** Torrent ETA (seconds) */
eta: number
/** Torrent hash */
hash: string
/** Torrent Infohash V1 */
infohash_v1: string
/** Torrent Infohash V2 */
infohash_v2: string
/** Whether torrent is private or not */
is_private: boolean
/** Last seen complete date (unix timestamp) */
last_seen: number
/** Torrent name */
name: string
/** Torrent connection count */
nb_connections: number
/** Torrent connection count limit */

View file

@ -38,7 +38,7 @@
<v-card-text class="pa-0">
<v-tabs-items v-model="tab" touchless>
<v-tab-item eager value="info">
<info v-if="torrent" :torrent="torrent" :hash="hash" />
<Info v-if="torrent" :torrent="torrent" :hash="hash" />
</v-tab-item>
<v-tab-item eager value="trackers">
<Trackers :is-active="tab === 'trackers'" :hash="hash" />
@ -69,8 +69,6 @@ export default {
data() {
return {
tab: null,
items: [{ tab: 'Info' }, { tab: 'Content' }],
peers: [],
mdiClose
}
},