]> git.immae.eu Git - github/bastienwirtz/homer.git/blob - src/components/services/Rtorrent.vue
Linting update
[github/bastienwirtz/homer.git] / src / components / services / Rtorrent.vue
1 <template>
2 <Generic :item="item">
3 <template #content>
4 <p class="title is-4">{{ item.name }}</p>
5 <p class="subtitle is-6">
6 <span v-if="error" class="error">An error has occurred.</span>
7 <template v-else>
8 <span class="down">
9 <i class="fas fa-download"></i> {{ downRate }}
10 </span>
11 <span class="up"> <i class="fas fa-upload"></i> {{ upRate }} </span>
12 </template>
13 </p>
14 </template>
15 <template #indicator>
16 <span v-if="!error" class="count"
17 >{{ count }}
18 <template v-if="count === 1">torrent</template>
19 <template v-else>torrents</template>
20 </span>
21 </template>
22 </Generic>
23 </template>
24
25 <script>
26 import Generic from "./Generic.vue";
27
28 // Units to add to download and upload rates.
29 const units = ["B", "kiB", "MiB", "GiB"];
30
31 // Take the rate in bytes and keep dividing it by 1k until the lowest
32 // value for which we have a unit is determined. Return the value with
33 // up to two decimals as a string and unit/s appended.
34 const displayRate = (rate) => {
35 let i = 0;
36
37 while (rate > 1000 && i < units.length) {
38 rate /= 1000;
39 i++;
40 }
41
42 return (
43 Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(
44 rate || 0,
45 ) + ` ${units[i]}/s`
46 );
47 };
48
49 export default {
50 name: "rTorrent",
51 props: { item: Object },
52 components: { Generic },
53 // Properties for download, upload, torrent count and errors.
54 data: () => ({ dl: null, ul: null, count: null, error: null }),
55 // Computed properties for the rate labels.
56 computed: {
57 downRate: function () {
58 return displayRate(this.dl);
59 },
60 upRate: function () {
61 return displayRate(this.ul);
62 },
63 },
64 created() {
65 // Set intervals if configured so the rates and/or torrent count
66 // will be updated.
67 const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
68 const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
69
70 if (rateInterval > 0) {
71 setInterval(() => this.fetchRates(), rateInterval);
72 }
73
74 if (torrentInterval > 0) {
75 setInterval(() => this.fetchCount(), torrentInterval);
76 }
77
78 // Fetch the initial values.
79 this.fetchRates();
80 this.fetchCount();
81 },
82 methods: {
83 // Perform two calls to the XML-RPC service and fetch download
84 // and upload rates. Values are saved to the `ul` and `dl`
85 // properties.
86 fetchRates: async function () {
87 this.getRate("throttle.global_up.rate")
88 .then((ul) => (this.ul = ul))
89 .catch(() => (this.error = true));
90
91 this.getRate("throttle.global_down.rate")
92 .then((dl) => (this.dl = dl))
93 .catch(() => (this.error = true));
94 },
95 // Perform a call to the XML-RPC service to fetch the number of
96 // torrents.
97 fetchCount: async function () {
98 this.getCount().catch(() => (this.error = true));
99 },
100 // Fetch a numeric value from the XML-RPC service by requesting
101 // the specified method name and parsing the XML. The response
102 // is expected to adhere to the structure of a single numeric
103 // value.
104 getRate: async function (methodName) {
105 return this.getXml(methodName).then((xml) =>
106 parseInt(
107 xml.getElementsByTagName("value")[0].firstChild.textContent,
108 10,
109 ),
110 );
111 },
112 // Fetch the numer of torrents by requesting the download list
113 // and counting the number of entries therein.
114 getCount: async function () {
115 return this.getXml("download_list").then((xml) => {
116 const arrayEl = xml.getElementsByTagName("array");
117 this.count = arrayEl
118 ? arrayEl[0].getElementsByTagName("value").length
119 : 0;
120 });
121 },
122 // Perform a call to the XML-RPC service and parse the response
123 // as XML, which is then returned.
124 getXml: async function (methodName) {
125 const headers = { "Content-Type": "text/xml" };
126
127 if (this.item.username && this.item.password) {
128 headers[
129 "Authorization"
130 ] = `${this.item.username}:${this.item.password}`;
131 }
132
133 return fetch(`${this.item.xmlrpc.replace(/\/$/, "")}/RPC2`, {
134 method: "POST",
135 headers,
136 body: `<methodCall><methodName>${methodName}</methodName></methodCall>`,
137 })
138 .then((response) => {
139 if (!response.ok) {
140 throw Error(response.statusText);
141 }
142
143 return response.text();
144 })
145 .then((text) =>
146 Promise.resolve(new DOMParser().parseFromString(text, "text/xml")),
147 );
148 },
149 },
150 };
151 </script>
152
153 <style scoped lang="scss">
154 .error {
155 color: #e51111 !important;
156 }
157 .down {
158 margin-right: 1em;
159 }
160 .count {
161 color: var(--text);
162 font-size: 0.8em;
163 }
164 </style>