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