2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2008 Christophe Dumez <chris@qbittorrent.org>
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.
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.
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.
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.
29 /* -----------------------------------------------------------------
31 ATTACH MOCHA LINK EVENTS
32 Notes: Here is where you define your windows and the events that open them.
33 If you are not using links to run Mocha methods you can remove this function.
35 If you need to add link events to links within windows you are creating, do
36 it in the onContentLoaded function of the new window.
38 ----------------------------------------------------------------- */
41 const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferencesClass();
43 let saveWindowSize = function() {};
44 let loadWindowWidth = function() {};
45 let loadWindowHeight = function() {};
46 let showDownloadPage = function() {};
47 let globalUploadLimitFN = function() {};
48 let uploadLimitFN = function() {};
49 let shareRatioFN = function() {};
50 let toggleSequentialDownloadFN = function() {};
51 let toggleFirstLastPiecePrioFN = function() {};
52 let setSuperSeedingFN = function() {};
53 let setForceStartFN = function() {};
54 let globalDownloadLimitFN = function() {};
55 let StatisticsLinkFN = function() {};
56 let downloadLimitFN = function() {};
57 let deleteFN = function() {};
58 let stopFN = function() {};
59 let startFN = function() {};
60 let autoTorrentManagementFN = function() {};
61 let recheckFN = function() {};
62 let reannounceFN = function() {};
63 let setLocationFN = function() {};
64 let renameFN = function() {};
65 let renameFilesFN = function() {};
66 let torrentNewCategoryFN = function() {};
67 let torrentSetCategoryFN = function() {};
68 let createCategoryFN = function() {};
69 let createSubcategoryFN = function() {};
70 let editCategoryFN = function() {};
71 let removeCategoryFN = function() {};
72 let deleteUnusedCategoriesFN = function() {};
73 let startTorrentsByCategoryFN = function() {};
74 let stopTorrentsByCategoryFN = function() {};
75 let deleteTorrentsByCategoryFN = function() {};
76 let torrentAddTagsFN = function() {};
77 let torrentSetTagsFN = function() {};
78 let torrentRemoveAllTagsFN = function() {};
79 let createTagFN = function() {};
80 let removeTagFN = function() {};
81 let deleteUnusedTagsFN = function() {};
82 let startTorrentsByTagFN = function() {};
83 let stopTorrentsByTagFN = function() {};
84 let deleteTorrentsByTagFN = function() {};
85 let startTorrentsByTrackerFN = function() {};
86 let stopTorrentsByTrackerFN = function() {};
87 let deleteTorrentsByTrackerFN = function() {};
88 let copyNameFN = function() {};
89 let copyInfohashFN = function(policy) {};
90 let copyMagnetLinkFN = function() {};
91 let copyIdFN = function() {};
92 let copyCommentFN = function() {};
93 let setQueuePositionFN = function() {};
94 let exportTorrentFN = function() {};
96 const initializeWindows = function() {
97 saveWindowSize = function(windowId) {
98 const size = $(windowId).getSize();
99 LocalPreferences.set("window_" + windowId + "_width", size.x);
100 LocalPreferences.set("window_" + windowId + "_height", size.y);
103 loadWindowWidth = function(windowId, defaultValue) {
104 return LocalPreferences.get("window_" + windowId + "_width", defaultValue);
107 loadWindowHeight = function(windowId, defaultValue) {
108 return LocalPreferences.get("window_" + windowId + "_height", defaultValue);
111 function addClickEvent(el, fn) {
112 ["Link", "Button"].each((item) => {
114 $(el + item).addEvent("click", fn);
119 addClickEvent("download", (e) => {
124 showDownloadPage = function(urls) {
125 const id = "downloadPage";
126 const contentUri = new URI("download.html");
128 if (urls && (urls.length > 0)) {
129 contentUri.setData("urls", urls.map(encodeURIComponent).join("|"));
134 title: "QBT_TR(Download from URLs)QBT_TR[CONTEXT=downloadFromURL]",
135 loadMethod: "iframe",
136 contentURL: contentUri.toString(),
137 addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
142 paddingHorizontal: 0,
143 width: loadWindowWidth(id, 500),
144 height: loadWindowHeight(id, 600),
145 onResize: function() {
152 addClickEvent("preferences", (e) => {
154 const id = "preferencesPage";
157 title: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
160 contentURL: new URI("views/preferences.html").toString(),
162 css: ["css/Tabs.css"]
164 toolbarURL: "views/preferencesToolbar.html",
168 paddingHorizontal: 0,
169 width: loadWindowWidth(id, 700),
170 height: loadWindowHeight(id, 600),
171 onResize: function() {
177 addClickEvent("upload", (e) => {
179 const id = "uploadPage";
182 title: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
183 loadMethod: "iframe",
184 contentURL: new URI("upload.html").toString(),
185 addClass: "windowFrame", // fixes iframe scrolling on iOS Safari
189 paddingHorizontal: 0,
190 width: loadWindowWidth(id, 500),
191 height: loadWindowHeight(id, 460),
192 onResize: function() {
199 globalUploadLimitFN = function() {
201 id: "uploadLimitPage",
202 title: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
203 loadMethod: "iframe",
204 contentURL: new URI("uploadlimit.html").setData("hashes", "global").toString(),
209 paddingHorizontal: 0,
215 uploadLimitFN = function() {
216 const hashes = torrentsTable.selectedRowsIds();
219 id: "uploadLimitPage",
220 title: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
221 loadMethod: "iframe",
222 contentURL: new URI("uploadlimit.html").setData("hashes", hashes.join("|")).toString(),
227 paddingHorizontal: 0,
234 shareRatioFN = function() {
235 const hashes = torrentsTable.selectedRowsIds();
237 let shareRatio = null;
238 let torrentsHaveSameShareRatio = true;
240 // check if all selected torrents have same share ratio
241 for (let i = 0; i < hashes.length; ++i) {
242 const hash = hashes[i];
243 const row = torrentsTable.rows[hash].full_data;
244 const origValues = row.ratio_limit + "|" + row.seeding_time_limit + "|" + row.inactive_seeding_time_limit + "|"
245 + row.max_ratio + "|" + row.max_seeding_time + "|" + row.max_inactive_seeding_time;
248 if (shareRatio === null)
249 shareRatio = origValues;
251 if (origValues !== shareRatio) {
252 torrentsHaveSameShareRatio = false;
257 // if all torrents have same share ratio, display that share ratio. else use the default
258 const orig = torrentsHaveSameShareRatio ? shareRatio : "";
260 id: "shareRatioPage",
261 title: "QBT_TR(Torrent Upload/Download Ratio Limiting)QBT_TR[CONTEXT=UpDownRatioDialog]",
262 loadMethod: "iframe",
263 contentURL: new URI("shareratio.html").setData("hashes", hashes.join("|")).setData("orig", orig).toString(),
267 paddingHorizontal: 0,
274 toggleSequentialDownloadFN = function() {
275 const hashes = torrentsTable.selectedRowsIds();
278 url: "api/v2/torrents/toggleSequentialDownload",
281 hashes: hashes.join("|")
288 toggleFirstLastPiecePrioFN = function() {
289 const hashes = torrentsTable.selectedRowsIds();
292 url: "api/v2/torrents/toggleFirstLastPiecePrio",
295 hashes: hashes.join("|")
302 setSuperSeedingFN = function(val) {
303 const hashes = torrentsTable.selectedRowsIds();
306 url: "api/v2/torrents/setSuperSeeding",
310 hashes: hashes.join("|")
317 setForceStartFN = function() {
318 const hashes = torrentsTable.selectedRowsIds();
321 url: "api/v2/torrents/setForceStart",
325 hashes: hashes.join("|")
332 globalDownloadLimitFN = function() {
334 id: "downloadLimitPage",
335 title: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
336 loadMethod: "iframe",
337 contentURL: new URI("downloadlimit.html").setData("hashes", "global").toString(),
342 paddingHorizontal: 0,
348 StatisticsLinkFN = function() {
349 const id = "statisticspage";
352 title: "QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]",
354 contentURL: new URI("views/statistics.html").toString(),
357 width: loadWindowWidth(id, 275),
358 height: loadWindowHeight(id, 370),
359 onResize: function() {
365 downloadLimitFN = function() {
366 const hashes = torrentsTable.selectedRowsIds();
369 id: "downloadLimitPage",
370 title: "QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
371 loadMethod: "iframe",
372 contentURL: new URI("downloadlimit.html").setData("hashes", hashes.join("|")).toString(),
377 paddingHorizontal: 0,
384 deleteFN = function(deleteFiles = false) {
385 const hashes = torrentsTable.selectedRowsIds();
388 id: "confirmDeletionPage",
389 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
390 loadMethod: "iframe",
391 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).setData("deleteFiles", deleteFiles).toString(),
403 addClickEvent("delete", (e) => {
408 stopFN = function() {
409 const hashes = torrentsTable.selectedRowsIds();
412 url: "api/v2/torrents/stop",
415 hashes: hashes.join("|")
422 startFN = function() {
423 const hashes = torrentsTable.selectedRowsIds();
426 url: "api/v2/torrents/start",
429 hashes: hashes.join("|")
436 autoTorrentManagementFN = function() {
437 const hashes = torrentsTable.selectedRowsIds();
440 hashes.each((hash, index) => {
441 const row = torrentsTable.rows[hash];
442 if (!row.full_data.auto_tmm)
446 url: "api/v2/torrents/setAutoManagement",
449 hashes: hashes.join("|"),
457 recheckFN = function() {
458 const hashes = torrentsTable.selectedRowsIds();
461 url: "api/v2/torrents/recheck",
464 hashes: hashes.join("|"),
471 reannounceFN = function() {
472 const hashes = torrentsTable.selectedRowsIds();
475 url: "api/v2/torrents/reannounce",
478 hashes: hashes.join("|"),
485 setLocationFN = function() {
486 const hashes = torrentsTable.selectedRowsIds();
488 const hash = hashes[0];
489 const row = torrentsTable.rows[hash];
492 id: "setLocationPage",
493 title: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
494 loadMethod: "iframe",
495 contentURL: new URI("setlocation.html").setData("hashes", hashes.join("|")).setData("path", encodeURIComponent(row.full_data.save_path)).toString(),
500 paddingHorizontal: 0,
507 renameFN = function() {
508 const hashes = torrentsTable.selectedRowsIds();
509 if (hashes.length === 1) {
510 const hash = hashes[0];
511 const row = torrentsTable.rows[hash];
515 title: "QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]",
516 loadMethod: "iframe",
517 contentURL: new URI("rename.html").setData("hash", hash).setData("name", row.full_data.name).toString(),
522 paddingHorizontal: 0,
530 renameFilesFN = function() {
531 const hashes = torrentsTable.selectedRowsIds();
532 if (hashes.length === 1) {
533 const hash = hashes[0];
534 const row = torrentsTable.rows[hash];
537 id: "multiRenamePage",
538 title: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
539 data: { hash: hash, selectedRows: [] },
541 contentURL: "rename_files.html",
546 paddingHorizontal: 0,
549 resizeLimit: { "x": [800], "y": [420] }
555 torrentNewCategoryFN = function() {
556 const action = "set";
557 const hashes = torrentsTable.selectedRowsIds();
560 id: "newCategoryPage",
561 title: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
562 loadMethod: "iframe",
563 contentURL: new URI("newcategory.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
568 paddingHorizontal: 0,
575 torrentSetCategoryFN = function(categoryHash) {
576 const hashes = torrentsTable.selectedRowsIds();
577 if (hashes.length <= 0)
580 const categoryName = category_list.has(categoryHash)
581 ? category_list.get(categoryHash).name
584 url: "api/v2/torrents/setCategory",
587 hashes: hashes.join("|"),
588 category: categoryName
593 createCategoryFN = function() {
594 const action = "create";
596 id: "newCategoryPage",
597 title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
598 loadMethod: "iframe",
599 contentURL: new URI("newcategory.html").setData("action", action).toString(),
604 paddingHorizontal: 0,
611 createSubcategoryFN = function(categoryHash) {
612 const action = "createSubcategory";
613 const categoryName = category_list.get(categoryHash).name + "/";
615 id: "newSubcategoryPage",
616 title: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
617 loadMethod: "iframe",
618 contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", categoryName).toString(),
623 paddingHorizontal: 0,
630 editCategoryFN = function(categoryHash) {
631 const action = "edit";
632 const category = category_list.get(categoryHash);
634 id: "editCategoryPage",
635 title: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
636 loadMethod: "iframe",
637 contentURL: new URI("newcategory.html").setData("action", action).setData("categoryName", category.name).setData("savePath", category.savePath).toString(),
642 paddingHorizontal: 0,
649 removeCategoryFN = function(categoryHash) {
650 const categoryName = category_list.get(categoryHash).name;
652 url: "api/v2/torrents/removeCategories",
655 categories: categoryName
658 setCategoryFilter(CATEGORIES_ALL);
661 deleteUnusedCategoriesFN = function() {
662 const categories = [];
663 category_list.forEach((category, hash) => {
664 if (torrentsTable.getFilteredTorrentsNumber("all", hash, TAGS_ALL, TRACKERS_ALL) === 0)
665 categories.push(category.name);
669 url: "api/v2/torrents/removeCategories",
672 categories: categories.join("\n")
675 setCategoryFilter(CATEGORIES_ALL);
678 startTorrentsByCategoryFN = function(categoryHash) {
679 const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
682 url: "api/v2/torrents/start",
685 hashes: hashes.join("|")
692 stopTorrentsByCategoryFN = function(categoryHash) {
693 const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
696 url: "api/v2/torrents/stop",
699 hashes: hashes.join("|")
706 deleteTorrentsByCategoryFN = function(categoryHash) {
707 const hashes = torrentsTable.getFilteredTorrentsHashes("all", categoryHash, TAGS_ALL, TRACKERS_ALL);
710 id: "confirmDeletionPage",
711 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
712 loadMethod: "iframe",
713 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
725 torrentAddTagsFN = function() {
726 const action = "set";
727 const hashes = torrentsTable.selectedRowsIds();
731 title: "QBT_TR(Add Tags)QBT_TR[CONTEXT=TransferListWidget]",
732 loadMethod: "iframe",
733 contentURL: new URI("newtag.html").setData("action", action).setData("hashes", hashes.join("|")).toString(),
738 paddingHorizontal: 0,
745 torrentSetTagsFN = function(tagHash, isSet) {
746 const hashes = torrentsTable.selectedRowsIds();
747 if (hashes.length <= 0)
750 const tagName = tagList.has(tagHash) ? tagList.get(tagHash).name : "";
752 url: (isSet ? "api/v2/torrents/addTags" : "api/v2/torrents/removeTags"),
755 hashes: hashes.join("|"),
761 torrentRemoveAllTagsFN = function() {
762 const hashes = torrentsTable.selectedRowsIds();
765 url: ("api/v2/torrents/removeTags"),
768 hashes: hashes.join("|"),
774 createTagFN = function() {
775 const action = "create";
778 title: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
779 loadMethod: "iframe",
780 contentURL: new URI("newtag.html").setData("action", action).toString(),
785 paddingHorizontal: 0,
792 removeTagFN = function(tagHash) {
793 const tagName = tagList.get(tagHash).name;
795 url: "api/v2/torrents/deleteTags",
801 setTagFilter(TAGS_ALL);
804 deleteUnusedTagsFN = function() {
806 tagList.forEach((tag, hash) => {
807 if (torrentsTable.getFilteredTorrentsNumber("all", CATEGORIES_ALL, hash, TRACKERS_ALL) === 0)
811 url: "api/v2/torrents/deleteTags",
817 setTagFilter(TAGS_ALL);
820 startTorrentsByTagFN = function(tagHash) {
821 const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
824 url: "api/v2/torrents/start",
827 hashes: hashes.join("|")
834 stopTorrentsByTagFN = function(tagHash) {
835 const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
838 url: "api/v2/torrents/stop",
841 hashes: hashes.join("|")
848 deleteTorrentsByTagFN = function(tagHash) {
849 const hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, tagHash, TRACKERS_ALL);
852 id: "confirmDeletionPage",
853 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
854 loadMethod: "iframe",
855 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
867 startTorrentsByTrackerFN = function(trackerHash) {
868 const trackerHashInt = Number.parseInt(trackerHash, 10);
870 switch (trackerHashInt) {
872 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
874 case TRACKERS_TRACKERLESS:
875 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
878 const uniqueTorrents = new Set();
879 for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
880 for (const torrent of torrents) {
881 uniqueTorrents.add(torrent);
884 hashes = [...uniqueTorrents];
889 if (hashes.length > 0) {
891 url: "api/v2/torrents/start",
894 hashes: hashes.join("|")
901 stopTorrentsByTrackerFN = function(trackerHash) {
902 const trackerHashInt = Number.parseInt(trackerHash, 10);
904 switch (trackerHashInt) {
906 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
908 case TRACKERS_TRACKERLESS:
909 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
912 const uniqueTorrents = new Set();
913 for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
914 for (const torrent of torrents) {
915 uniqueTorrents.add(torrent);
918 hashes = [...uniqueTorrents];
925 url: "api/v2/torrents/stop",
928 hashes: hashes.join("|")
935 deleteTorrentsByTrackerFN = function(trackerHash) {
936 const trackerHashInt = Number.parseInt(trackerHash, 10);
938 switch (trackerHashInt) {
940 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_ALL);
942 case TRACKERS_TRACKERLESS:
943 hashes = torrentsTable.getFilteredTorrentsHashes("all", CATEGORIES_ALL, TAGS_ALL, TRACKERS_TRACKERLESS);
946 const uniqueTorrents = new Set();
947 for (const torrents of trackerList.get(trackerHashInt).trackerTorrentMap.values()) {
948 for (const torrent of torrents) {
949 uniqueTorrents.add(torrent);
952 hashes = [...uniqueTorrents];
959 id: "confirmDeletionPage",
960 title: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
961 loadMethod: "iframe",
962 contentURL: new URI("confirmdeletion.html").setData("hashes", hashes.join("|")).toString(),
969 onCloseComplete: function() {
971 setTrackerFilter(TRACKERS_ALL);
977 copyNameFN = function() {
978 const selectedRows = torrentsTable.selectedRowsIds();
980 if (selectedRows.length > 0) {
981 const rows = torrentsTable.getFilteredAndSortedRows();
982 for (let i = 0; i < selectedRows.length; ++i) {
983 const hash = selectedRows[i];
984 names.push(rows[hash].full_data.name);
987 return names.join("\n");
990 copyInfohashFN = function(policy) {
991 const selectedRows = torrentsTable.selectedRowsIds();
992 const infohashes = [];
993 if (selectedRows.length > 0) {
994 const rows = torrentsTable.getFilteredAndSortedRows();
997 for (const id of selectedRows) {
998 const infohash = rows[id].full_data.infohash_v1;
1000 infohashes.push(infohash);
1004 for (const id of selectedRows) {
1005 const infohash = rows[id].full_data.infohash_v2;
1006 if (infohash !== "")
1007 infohashes.push(infohash);
1012 return infohashes.join("\n");
1015 copyMagnetLinkFN = function() {
1016 const selectedRows = torrentsTable.selectedRowsIds();
1018 if (selectedRows.length > 0) {
1019 const rows = torrentsTable.getFilteredAndSortedRows();
1020 for (let i = 0; i < selectedRows.length; ++i) {
1021 const hash = selectedRows[i];
1022 magnets.push(rows[hash].full_data.magnet_uri);
1025 return magnets.join("\n");
1028 copyIdFN = function() {
1029 return torrentsTable.selectedRowsIds().join("\n");
1032 copyCommentFN = function() {
1033 const selectedRows = torrentsTable.selectedRowsIds();
1034 const comments = [];
1035 if (selectedRows.length > 0) {
1036 const rows = torrentsTable.getFilteredAndSortedRows();
1037 for (let i = 0; i < selectedRows.length; ++i) {
1038 const hash = selectedRows[i];
1039 const comment = rows[hash].full_data.comment;
1040 if (comment && (comment !== ""))
1041 comments.push(comment);
1044 return comments.join("\n---------\n");
1047 exportTorrentFN = async function() {
1048 const hashes = torrentsTable.selectedRowsIds();
1049 for (const hash of hashes) {
1050 const row = torrentsTable.rows.get(hash);
1054 const name = row.full_data.name;
1055 const url = new URI("api/v2/torrents/export");
1056 url.setData("hash", hash);
1058 // download response to file
1059 const element = document.createElement("a");
1060 element.setAttribute("href", url);
1061 element.setAttribute("download", (name + ".torrent"));
1062 document.body.appendChild(element);
1064 document.body.removeChild(element);
1066 // https://stackoverflow.com/questions/53560991/automatic-file-downloads-limited-to-10-files-on-chrome-browser
1067 await window.qBittorrent.Misc.sleep(200);
1071 addClickEvent("stopAll", (e) => {
1072 new Event(e).stop();
1074 if (confirm("QBT_TR(Would you like to stop all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1076 url: "api/v2/torrents/stop",
1086 addClickEvent("startAll", (e) => {
1087 new Event(e).stop();
1089 if (confirm("QBT_TR(Would you like to start all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1091 url: "api/v2/torrents/start",
1101 ["stop", "start", "recheck"].each((item) => {
1102 addClickEvent(item, (e) => {
1103 new Event(e).stop();
1104 const hashes = torrentsTable.selectedRowsIds();
1105 if (hashes.length) {
1106 hashes.each((hash, index) => {
1108 url: "api/v2/torrents/" + item,
1120 ["decreasePrio", "increasePrio", "topPrio", "bottomPrio"].each((item) => {
1121 addClickEvent(item, (e) => {
1122 new Event(e).stop();
1123 setQueuePositionFN(item);
1127 setQueuePositionFN = function(cmd) {
1128 const hashes = torrentsTable.selectedRowsIds();
1129 if (hashes.length) {
1131 url: "api/v2/torrents/" + cmd,
1134 hashes: hashes.join("|")
1141 addClickEvent("about", (e) => {
1142 new Event(e).stop();
1143 const id = "aboutpage";
1144 new MochaUI.Window({
1146 title: "QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]",
1148 contentURL: new URI("views/about.html").toString(),
1150 css: ["css/Tabs.css"]
1153 toolbarURL: "views/aboutToolbar.html",
1155 width: loadWindowWidth(id, 550),
1156 height: loadWindowHeight(id, 360),
1157 onResize: function() {
1163 addClickEvent("logout", (e) => {
1164 new Event(e).stop();
1166 url: "api/v2/auth/logout",
1168 onSuccess: function() {
1169 window.location.reload(true);
1174 addClickEvent("shutdown", (e) => {
1175 new Event(e).stop();
1176 if (confirm("QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]")) {
1178 url: "api/v2/app/shutdown",
1180 onSuccess: function() {
1181 const shutdownMessage = "QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]".replace("%1", window.qBittorrent.Client.mainTitle());
1182 document.write(`<!doctype html><html lang="${LANG}"><head> <meta charset="UTF-8"> <meta name="color-scheme" content="light dark"> <title>${shutdownMessage}</title> <style>* {font-family: Arial, Helvetica, sans-serif;}</style></head><body> <h1 style="text-align: center;">${shutdownMessage}</h1></body></html>`);
1185 window.qBittorrent.Client.stop();
1191 // Deactivate menu header links
1192 $$("a.returnFalse").each((el) => {
1193 el.addEvent("click", (e) => {
1194 new Event(e).stop();