Enable customizing the save statistics time interval
[qBittorrent.git] / src / webui / www / private / scripts / misc.js
blob4fff5d4feee957db8ab981d6d9fe6151be6c8a1e
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2014  Gabriele <pmzqla.git@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL".  If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
29 "use strict";
31 window.qBittorrent ??= {};
32 window.qBittorrent.Misc ??= (() => {
33     const exports = () => {
34         return {
35             genHash: genHash,
36             getHost: getHost,
37             createDebounceHandler: createDebounceHandler,
38             friendlyUnit: friendlyUnit,
39             friendlyDuration: friendlyDuration,
40             friendlyPercentage: friendlyPercentage,
41             friendlyFloat: friendlyFloat,
42             parseHtmlLinks: parseHtmlLinks,
43             parseVersion: parseVersion,
44             escapeHtml: escapeHtml,
45             naturalSortCollator: naturalSortCollator,
46             safeTrim: safeTrim,
47             toFixedPointString: toFixedPointString,
48             containsAllTerms: containsAllTerms,
49             sleep: sleep,
50             // variables
51             FILTER_INPUT_DELAY: 400,
52             MAX_ETA: 8640000
53         };
54     };
56     const genHash = function(string) {
57         // origins:
58         // https://stackoverflow.com/a/8831937
59         // https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
60         let hash = 0;
61         for (let i = 0; i < string.length; ++i)
62             hash = ((Math.imul(hash, 31) + string.charCodeAt(i)) | 0);
63         return hash;
64     };
66     // getHost emulate the GUI version `QString getHost(const QString &url)`
67     const getHost = function(url) {
68         // We want the hostname.
69         // If failed to parse the domain, original input should be returned
71         if (!/^(?:https?|udp):/i.test(url))
72             return url;
74         try {
75             // hack: URL can not get hostname from udp protocol
76             const parsedUrl = new URL(url.replace(/^udp:/i, "https:"));
77             // host: "example.com:8443"
78             // hostname: "example.com"
79             const host = parsedUrl.hostname;
80             if (!host)
81                 return url;
83             return host;
84         }
85         catch (error) {
86             return url;
87         }
88     };
90     const createDebounceHandler = (delay, func) => {
91         let timer = -1;
92         return (...params) => {
93             clearTimeout(timer);
94             timer = setTimeout(() => {
95                 func(...params);
97                 timer = -1;
98             }, delay);
99         };
100     };
102     /*
103      * JS counterpart of the function in src/misc.cpp
104      */
105     const friendlyUnit = function(value, isSpeed) {
106         const units = [
107             "QBT_TR(B)QBT_TR[CONTEXT=misc]",
108             "QBT_TR(KiB)QBT_TR[CONTEXT=misc]",
109             "QBT_TR(MiB)QBT_TR[CONTEXT=misc]",
110             "QBT_TR(GiB)QBT_TR[CONTEXT=misc]",
111             "QBT_TR(TiB)QBT_TR[CONTEXT=misc]",
112             "QBT_TR(PiB)QBT_TR[CONTEXT=misc]",
113             "QBT_TR(EiB)QBT_TR[CONTEXT=misc]"
114         ];
116         if ((value === undefined) || (value === null) || (value < 0))
117             return "QBT_TR(Unknown)QBT_TR[CONTEXT=misc]";
119         let i = 0;
120         while ((value >= 1024.0) && (i < 6)) {
121             value /= 1024.0;
122             ++i;
123         }
125         function friendlyUnitPrecision(sizeUnit) {
126             if (sizeUnit <= 2) // KiB, MiB
127                 return 1;
128             else if (sizeUnit === 3) // GiB
129                 return 2;
130             else // TiB, PiB, EiB
131                 return 3;
132         }
134         let ret;
135         if (i === 0) {
136             ret = value + " " + units[i];
137         }
138         else {
139             const precision = friendlyUnitPrecision(i);
140             const offset = Math.pow(10, precision);
141             // Don't round up
142             ret = (Math.floor(offset * value) / offset).toFixed(precision) + " " + units[i];
143         }
145         if (isSpeed)
146             ret += "QBT_TR(/s)QBT_TR[CONTEXT=misc]";
147         return ret;
148     };
150     /*
151      * JS counterpart of the function in src/misc.cpp
152      */
153     const friendlyDuration = function(seconds, maxCap = -1) {
154         if ((seconds < 0) || ((seconds >= maxCap) && (maxCap >= 0)))
155             return "∞";
156         if (seconds === 0)
157             return "0";
158         if (seconds < 60)
159             return "QBT_TR(< 1m)QBT_TR[CONTEXT=misc]";
160         let minutes = seconds / 60;
161         if (minutes < 60)
162             return "QBT_TR(%1m)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(minutes));
163         let hours = minutes / 60;
164         minutes %= 60;
165         if (hours < 24)
166             return "QBT_TR(%1h %2m)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(hours)).replace("%2", Math.floor(minutes));
167         let days = hours / 24;
168         hours %= 24;
169         if (days < 365)
170             return "QBT_TR(%1d %2h)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(days)).replace("%2", Math.floor(hours));
171         const years = days / 365;
172         days %= 365;
173         return "QBT_TR(%1y %2d)QBT_TR[CONTEXT=misc]".replace("%1", Math.floor(years)).replace("%2", Math.floor(days));
174     };
176     const friendlyPercentage = function(value) {
177         let percentage = (value * 100).round(1);
178         if (isNaN(percentage) || (percentage < 0))
179             percentage = 0;
180         if (percentage > 100)
181             percentage = 100;
182         return percentage.toFixed(1) + "%";
183     };
185     const friendlyFloat = function(value, precision) {
186         return parseFloat(value).toFixed(precision);
187     };
189     /*
190      * JS counterpart of the function in src/misc.cpp
191      */
192     const parseHtmlLinks = function(text) {
193         const exp = /(\b(https?|ftp|file):\/\/[-\w+&@#/%?=~|!:,.;]*[-\w+&@#/%=~|])/gi;
194         return text.replace(exp, "<a target='_blank' rel='noopener noreferrer' href='$1'>$1</a>");
195     };
197     const parseVersion = function(versionString) {
198         const failure = {
199             valid: false
200         };
202         if (typeof versionString !== "string")
203             return failure;
205         const tryToNumber = (str) => {
206             const num = Number(str);
207             return (isNaN(num) ? str : num);
208         };
210         const ver = versionString.split(".", 4).map(val => tryToNumber(val));
211         return {
212             valid: true,
213             major: ver[0],
214             minor: ver[1],
215             fix: ver[2],
216             patch: ver[3]
217         };
218     };
220     const escapeHtml = (() => {
221         const div = document.createElement("div");
222         return (str) => {
223             div.textContent = str;
224             return div.innerHTML;
225         };
226     })();
228     // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#parameters
229     const naturalSortCollator = new Intl.Collator(undefined, { numeric: true, usage: "sort" });
231     const safeTrim = function(value) {
232         try {
233             return value.trim();
234         }
235         catch (e) {
236             if (e instanceof TypeError)
237                 return "";
238             throw e;
239         }
240     };
242     const toFixedPointString = function(number, digits) {
243         // Do not round up number
244         const power = Math.pow(10, digits);
245         return (Math.floor(power * number) / power).toFixed(digits);
246     };
248     /**
249      *
250      * @param {String} text the text to search
251      * @param {Array<String>} terms terms to search for within the text
252      * @returns {Boolean} true if all terms match the text, false otherwise
253      */
254     const containsAllTerms = function(text, terms) {
255         const textToSearch = text.toLowerCase();
256         return terms.every((term) => {
257             const isTermRequired = (term[0] === "+");
258             const isTermExcluded = (term[0] === "-");
259             if (isTermRequired || isTermExcluded) {
260                 // ignore lonely +/-
261                 if (term.length === 1)
262                     return true;
264                 term = term.substring(1);
265             }
267             const textContainsTerm = (textToSearch.indexOf(term) !== -1);
268             return isTermExcluded ? !textContainsTerm : textContainsTerm;
269         });
270     };
272     const sleep = (ms) => {
273         return new Promise((resolve) => {
274             setTimeout(resolve, ms);
275         });
276     };
278     return exports();
279 })();
280 Object.freeze(window.qBittorrent.Misc);