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
).addEventListener("click", fn
);
118 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("|"));
133 icon
: "images/qbittorrent-tray.svg",
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
) => {
156 const id
= "preferencesPage";
159 icon
: "images/qbittorrent-tray.svg",
160 title
: "QBT_TR(Options)QBT_TR[CONTEXT=OptionsDialog]",
163 contentURL
: new URI("views/preferences.html").toString(),
165 css
: ["css/Tabs.css"]
167 toolbarURL
: "views/preferencesToolbar.html",
171 paddingHorizontal
: 0,
172 width
: loadWindowWidth(id
, 700),
173 height
: loadWindowHeight(id
, 600),
174 onResize: function() {
180 addClickEvent("upload", (e
) => {
184 const id
= "uploadPage";
187 icon
: "images/qbittorrent-tray.svg",
188 title
: "QBT_TR(Upload local torrent)QBT_TR[CONTEXT=HttpServer]",
189 loadMethod
: "iframe",
190 contentURL
: new URI("upload.html").toString(),
191 addClass
: "windowFrame", // fixes iframe scrolling on iOS Safari
195 paddingHorizontal
: 0,
196 width
: loadWindowWidth(id
, 500),
197 height
: loadWindowHeight(id
, 460),
198 onResize: function() {
205 globalUploadLimitFN = function() {
207 id
: "uploadLimitPage",
208 icon
: "images/qbittorrent-tray.svg",
209 title
: "QBT_TR(Global Upload Speed Limit)QBT_TR[CONTEXT=MainWindow]",
210 loadMethod
: "iframe",
211 contentURL
: new URI("uploadlimit.html").setData("hashes", "global").toString(),
216 paddingHorizontal
: 0,
222 uploadLimitFN = function() {
223 const hashes
= torrentsTable
.selectedRowsIds();
226 id
: "uploadLimitPage",
227 icon
: "images/qbittorrent-tray.svg",
228 title
: "QBT_TR(Torrent Upload Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
229 loadMethod
: "iframe",
230 contentURL
: new URI("uploadlimit.html").setData("hashes", hashes
.join("|")).toString(),
235 paddingHorizontal
: 0,
242 shareRatioFN = function() {
243 const hashes
= torrentsTable
.selectedRowsIds();
245 let shareRatio
= null;
246 let torrentsHaveSameShareRatio
= true;
248 // check if all selected torrents have same share ratio
249 for (let i
= 0; i
< hashes
.length
; ++i
) {
250 const hash
= hashes
[i
];
251 const row
= torrentsTable
.rows
[hash
].full_data
;
252 const origValues
= row
.ratio_limit
+ "|" + row
.seeding_time_limit
+ "|" + row
.inactive_seeding_time_limit
+ "|"
253 + row
.max_ratio
+ "|" + row
.max_seeding_time
+ "|" + row
.max_inactive_seeding_time
;
256 if (shareRatio
=== null)
257 shareRatio
= origValues
;
259 if (origValues
!== shareRatio
) {
260 torrentsHaveSameShareRatio
= false;
265 // if all torrents have same share ratio, display that share ratio. else use the default
266 const orig
= torrentsHaveSameShareRatio
? shareRatio
: "";
268 id
: "shareRatioPage",
269 icon
: "images/qbittorrent-tray.svg",
270 title
: "QBT_TR(Torrent Upload/Download Ratio Limiting)QBT_TR[CONTEXT=UpDownRatioDialog]",
271 loadMethod
: "iframe",
272 contentURL
: new URI("shareratio.html").setData("hashes", hashes
.join("|")).setData("orig", orig
).toString(),
276 paddingHorizontal
: 0,
283 toggleSequentialDownloadFN = function() {
284 const hashes
= torrentsTable
.selectedRowsIds();
287 url
: "api/v2/torrents/toggleSequentialDownload",
290 hashes
: hashes
.join("|")
297 toggleFirstLastPiecePrioFN = function() {
298 const hashes
= torrentsTable
.selectedRowsIds();
301 url
: "api/v2/torrents/toggleFirstLastPiecePrio",
304 hashes
: hashes
.join("|")
311 setSuperSeedingFN = function(val
) {
312 const hashes
= torrentsTable
.selectedRowsIds();
315 url
: "api/v2/torrents/setSuperSeeding",
319 hashes
: hashes
.join("|")
326 setForceStartFN = function() {
327 const hashes
= torrentsTable
.selectedRowsIds();
330 url
: "api/v2/torrents/setForceStart",
334 hashes
: hashes
.join("|")
341 globalDownloadLimitFN = function() {
343 id
: "downloadLimitPage",
344 icon
: "images/qbittorrent-tray.svg",
345 title
: "QBT_TR(Global Download Speed Limit)QBT_TR[CONTEXT=MainWindow]",
346 loadMethod
: "iframe",
347 contentURL
: new URI("downloadlimit.html").setData("hashes", "global").toString(),
352 paddingHorizontal
: 0,
358 StatisticsLinkFN = function() {
359 const id
= "statisticspage";
362 icon
: "images/qbittorrent-tray.svg",
363 title
: "QBT_TR(Statistics)QBT_TR[CONTEXT=StatsDialog]",
365 contentURL
: new URI("views/statistics.html").toString(),
368 width
: loadWindowWidth(id
, 275),
369 height
: loadWindowHeight(id
, 370),
370 onResize: function() {
376 downloadLimitFN = function() {
377 const hashes
= torrentsTable
.selectedRowsIds();
380 id
: "downloadLimitPage",
381 icon
: "images/qbittorrent-tray.svg",
382 title
: "QBT_TR(Torrent Download Speed Limiting)QBT_TR[CONTEXT=TransferListWidget]",
383 loadMethod
: "iframe",
384 contentURL
: new URI("downloadlimit.html").setData("hashes", hashes
.join("|")).toString(),
389 paddingHorizontal
: 0,
396 deleteFN = function(deleteFiles
= false) {
397 const hashes
= torrentsTable
.selectedRowsIds();
400 id
: "confirmDeletionPage",
401 icon
: "images/qbittorrent-tray.svg",
402 title
: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
403 loadMethod
: "iframe",
404 contentURL
: new URI("confirmdeletion.html").setData("hashes", hashes
.join("|")).setData("deleteFiles", deleteFiles
).toString(),
416 addClickEvent("delete", (e
) => {
422 stopFN = function() {
423 const hashes
= torrentsTable
.selectedRowsIds();
426 url
: "api/v2/torrents/stop",
429 hashes
: hashes
.join("|")
436 startFN = function() {
437 const hashes
= torrentsTable
.selectedRowsIds();
440 url
: "api/v2/torrents/start",
443 hashes
: hashes
.join("|")
450 autoTorrentManagementFN = function() {
451 const hashes
= torrentsTable
.selectedRowsIds();
454 hashes
.each((hash
, index
) => {
455 const row
= torrentsTable
.rows
[hash
];
456 if (!row
.full_data
.auto_tmm
)
460 url
: "api/v2/torrents/setAutoManagement",
463 hashes
: hashes
.join("|"),
471 recheckFN = function() {
472 const hashes
= torrentsTable
.selectedRowsIds();
475 url
: "api/v2/torrents/recheck",
478 hashes
: hashes
.join("|"),
485 reannounceFN = function() {
486 const hashes
= torrentsTable
.selectedRowsIds();
489 url
: "api/v2/torrents/reannounce",
492 hashes
: hashes
.join("|"),
499 setLocationFN = function() {
500 const hashes
= torrentsTable
.selectedRowsIds();
502 const hash
= hashes
[0];
503 const row
= torrentsTable
.rows
[hash
];
506 id
: "setLocationPage",
507 icon
: "images/qbittorrent-tray.svg",
508 title
: "QBT_TR(Set location)QBT_TR[CONTEXT=TransferListWidget]",
509 loadMethod
: "iframe",
510 contentURL
: new URI("setlocation.html").setData("hashes", hashes
.join("|")).setData("path", encodeURIComponent(row
.full_data
.save_path
)).toString(),
515 paddingHorizontal
: 0,
522 renameFN = function() {
523 const hashes
= torrentsTable
.selectedRowsIds();
524 if (hashes
.length
=== 1) {
525 const hash
= hashes
[0];
526 const row
= torrentsTable
.rows
[hash
];
530 icon
: "images/qbittorrent-tray.svg",
531 title
: "QBT_TR(Rename)QBT_TR[CONTEXT=TransferListWidget]",
532 loadMethod
: "iframe",
533 contentURL
: new URI("rename.html").setData("hash", hash
).setData("name", row
.full_data
.name
).toString(),
538 paddingHorizontal
: 0,
546 renameFilesFN = function() {
547 const hashes
= torrentsTable
.selectedRowsIds();
548 if (hashes
.length
=== 1) {
549 const hash
= hashes
[0];
550 const row
= torrentsTable
.rows
[hash
];
553 id
: "multiRenamePage",
554 icon
: "images/qbittorrent-tray.svg",
555 title
: "QBT_TR(Renaming)QBT_TR[CONTEXT=TransferListWidget]",
556 data
: { hash
: hash
, selectedRows
: [] },
558 contentURL
: "rename_files.html",
563 paddingHorizontal
: 0,
566 resizeLimit
: { "x": [800], "y": [420] }
572 torrentNewCategoryFN = function() {
573 const action
= "set";
574 const hashes
= torrentsTable
.selectedRowsIds();
577 id
: "newCategoryPage",
578 icon
: "images/qbittorrent-tray.svg",
579 title
: "QBT_TR(New Category)QBT_TR[CONTEXT=TransferListWidget]",
580 loadMethod
: "iframe",
581 contentURL
: new URI("newcategory.html").setData("action", action
).setData("hashes", hashes
.join("|")).toString(),
586 paddingHorizontal
: 0,
589 onCloseComplete: function() {
596 torrentSetCategoryFN = function(categoryHash
) {
597 const hashes
= torrentsTable
.selectedRowsIds();
598 if (hashes
.length
<= 0)
601 const categoryName
= category_list
.has(categoryHash
)
602 ? category_list
.get(categoryHash
).name
605 url
: "api/v2/torrents/setCategory",
608 hashes
: hashes
.join("|"),
609 category
: categoryName
611 onSuccess: function() {
617 createCategoryFN = function() {
618 const action
= "create";
620 id
: "newCategoryPage",
621 icon
: "images/qbittorrent-tray.svg",
622 title
: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
623 loadMethod
: "iframe",
624 contentURL
: new URI("newcategory.html").setData("action", action
).toString(),
629 paddingHorizontal
: 0,
636 createSubcategoryFN = function(categoryHash
) {
637 const action
= "createSubcategory";
638 const categoryName
= category_list
.get(categoryHash
).name
+ "/";
640 id
: "newSubcategoryPage",
641 icon
: "images/qbittorrent-tray.svg",
642 title
: "QBT_TR(New Category)QBT_TR[CONTEXT=CategoryFilterWidget]",
643 loadMethod
: "iframe",
644 contentURL
: new URI("newcategory.html").setData("action", action
).setData("categoryName", categoryName
).toString(),
649 paddingHorizontal
: 0,
656 editCategoryFN = function(categoryHash
) {
657 const action
= "edit";
658 const category
= category_list
.get(categoryHash
);
660 id
: "editCategoryPage",
661 icon
: "images/qbittorrent-tray.svg",
662 title
: "QBT_TR(Edit Category)QBT_TR[CONTEXT=TransferListWidget]",
663 loadMethod
: "iframe",
664 contentURL
: new URI("newcategory.html").setData("action", action
).setData("categoryName", category
.name
).setData("savePath", category
.savePath
).toString(),
669 paddingHorizontal
: 0,
676 removeCategoryFN = function(categoryHash
) {
677 const categoryName
= category_list
.get(categoryHash
).name
;
679 url
: "api/v2/torrents/removeCategories",
682 categories
: categoryName
685 setCategoryFilter(CATEGORIES_ALL
);
688 deleteUnusedCategoriesFN = function() {
689 const categories
= [];
690 category_list
.forEach((category
, hash
) => {
691 if (torrentsTable
.getFilteredTorrentsNumber("all", hash
, TAGS_ALL
, TRACKERS_ALL
) === 0)
692 categories
.push(category
.name
);
696 url
: "api/v2/torrents/removeCategories",
699 categories
: categories
.join("\n")
702 setCategoryFilter(CATEGORIES_ALL
);
705 startTorrentsByCategoryFN = function(categoryHash
) {
706 const hashes
= torrentsTable
.getFilteredTorrentsHashes("all", categoryHash
, TAGS_ALL
, TRACKERS_ALL
);
709 url
: "api/v2/torrents/start",
712 hashes
: hashes
.join("|")
719 stopTorrentsByCategoryFN = function(categoryHash
) {
720 const hashes
= torrentsTable
.getFilteredTorrentsHashes("all", categoryHash
, TAGS_ALL
, TRACKERS_ALL
);
723 url
: "api/v2/torrents/stop",
726 hashes
: hashes
.join("|")
733 deleteTorrentsByCategoryFN = function(categoryHash
) {
734 const hashes
= torrentsTable
.getFilteredTorrentsHashes("all", categoryHash
, TAGS_ALL
, TRACKERS_ALL
);
737 id
: "confirmDeletionPage",
738 icon
: "images/qbittorrent-tray.svg",
739 title
: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
740 loadMethod
: "iframe",
741 contentURL
: new URI("confirmdeletion.html").setData("hashes", hashes
.join("|")).toString(),
753 torrentAddTagsFN = function() {
754 const action
= "set";
755 const hashes
= torrentsTable
.selectedRowsIds();
759 icon
: "images/qbittorrent-tray.svg",
760 title
: "QBT_TR(Add tags)QBT_TR[CONTEXT=TransferListWidget]",
761 loadMethod
: "iframe",
762 contentURL
: new URI("newtag.html").setData("action", action
).setData("hashes", hashes
.join("|")).toString(),
767 paddingHorizontal
: 0,
774 torrentSetTagsFN = function(tagHash
, isSet
) {
775 const hashes
= torrentsTable
.selectedRowsIds();
776 if (hashes
.length
<= 0)
779 const tagName
= tagList
.has(tagHash
) ? tagList
.get(tagHash
).name
: "";
781 url
: (isSet
? "api/v2/torrents/addTags" : "api/v2/torrents/removeTags"),
784 hashes
: hashes
.join("|"),
790 torrentRemoveAllTagsFN = function() {
791 const hashes
= torrentsTable
.selectedRowsIds();
794 url
: ("api/v2/torrents/removeTags"),
797 hashes
: hashes
.join("|"),
803 createTagFN = function() {
804 const action
= "create";
807 icon
: "images/qbittorrent-tray.svg",
808 title
: "QBT_TR(New Tag)QBT_TR[CONTEXT=TagFilterWidget]",
809 loadMethod
: "iframe",
810 contentURL
: new URI("newtag.html").setData("action", action
).toString(),
815 paddingHorizontal
: 0,
822 removeTagFN = function(tagHash
) {
823 const tagName
= tagList
.get(tagHash
).name
;
825 url
: "api/v2/torrents/deleteTags",
831 setTagFilter(TAGS_ALL
);
834 deleteUnusedTagsFN = function() {
836 tagList
.forEach((tag
, hash
) => {
837 if (torrentsTable
.getFilteredTorrentsNumber("all", CATEGORIES_ALL
, hash
, TRACKERS_ALL
) === 0)
841 url
: "api/v2/torrents/deleteTags",
847 setTagFilter(TAGS_ALL
);
850 startTorrentsByTagFN = function(tagHash
) {
851 const hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, tagHash
, TRACKERS_ALL
);
854 url
: "api/v2/torrents/start",
857 hashes
: hashes
.join("|")
864 stopTorrentsByTagFN = function(tagHash
) {
865 const hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, tagHash
, TRACKERS_ALL
);
868 url
: "api/v2/torrents/stop",
871 hashes
: hashes
.join("|")
878 deleteTorrentsByTagFN = function(tagHash
) {
879 const hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, tagHash
, TRACKERS_ALL
);
882 id
: "confirmDeletionPage",
883 icon
: "images/qbittorrent-tray.svg",
884 title
: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
885 loadMethod
: "iframe",
886 contentURL
: new URI("confirmdeletion.html").setData("hashes", hashes
.join("|")).toString(),
898 startTorrentsByTrackerFN = function(trackerHash
) {
899 const trackerHashInt
= Number
.parseInt(trackerHash
, 10);
901 switch (trackerHashInt
) {
903 hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, TAGS_ALL
, TRACKERS_ALL
);
905 case TRACKERS_TRACKERLESS
:
906 hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, TAGS_ALL
, TRACKERS_TRACKERLESS
);
909 const uniqueTorrents
= new Set();
910 for (const torrents
of trackerList
.get(trackerHashInt
).trackerTorrentMap
.values()) {
911 for (const torrent
of torrents
)
912 uniqueTorrents
.add(torrent
);
914 hashes
= [...uniqueTorrents
];
919 if (hashes
.length
> 0) {
921 url
: "api/v2/torrents/start",
924 hashes
: hashes
.join("|")
931 stopTorrentsByTrackerFN = function(trackerHash
) {
932 const trackerHashInt
= Number
.parseInt(trackerHash
, 10);
934 switch (trackerHashInt
) {
936 hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, TAGS_ALL
, TRACKERS_ALL
);
938 case TRACKERS_TRACKERLESS
:
939 hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, TAGS_ALL
, TRACKERS_TRACKERLESS
);
942 const uniqueTorrents
= new Set();
943 for (const torrents
of trackerList
.get(trackerHashInt
).trackerTorrentMap
.values()) {
944 for (const torrent
of torrents
)
945 uniqueTorrents
.add(torrent
);
947 hashes
= [...uniqueTorrents
];
954 url
: "api/v2/torrents/stop",
957 hashes
: hashes
.join("|")
964 deleteTorrentsByTrackerFN = function(trackerHash
) {
965 const trackerHashInt
= Number
.parseInt(trackerHash
, 10);
967 switch (trackerHashInt
) {
969 hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, TAGS_ALL
, TRACKERS_ALL
);
971 case TRACKERS_TRACKERLESS
:
972 hashes
= torrentsTable
.getFilteredTorrentsHashes("all", CATEGORIES_ALL
, TAGS_ALL
, TRACKERS_TRACKERLESS
);
975 const uniqueTorrents
= new Set();
976 for (const torrents
of trackerList
.get(trackerHashInt
).trackerTorrentMap
.values()) {
977 for (const torrent
of torrents
)
978 uniqueTorrents
.add(torrent
);
980 hashes
= [...uniqueTorrents
];
987 id
: "confirmDeletionPage",
988 icon
: "images/qbittorrent-tray.svg",
989 title
: "QBT_TR(Remove torrent(s))QBT_TR[CONTEXT=confirmDeletionDlg]",
990 loadMethod
: "iframe",
991 contentURL
: new URI("confirmdeletion.html").setData("hashes", hashes
.join("|")).toString(),
998 onCloseComplete: function() {
1000 setTrackerFilter(TRACKERS_ALL
);
1006 copyNameFN = function() {
1007 const selectedRows
= torrentsTable
.selectedRowsIds();
1009 if (selectedRows
.length
> 0) {
1010 const rows
= torrentsTable
.getFilteredAndSortedRows();
1011 for (let i
= 0; i
< selectedRows
.length
; ++i
) {
1012 const hash
= selectedRows
[i
];
1013 names
.push(rows
[hash
].full_data
.name
);
1016 return names
.join("\n");
1019 copyInfohashFN = function(policy
) {
1020 const selectedRows
= torrentsTable
.selectedRowsIds();
1021 const infohashes
= [];
1022 if (selectedRows
.length
> 0) {
1023 const rows
= torrentsTable
.getFilteredAndSortedRows();
1026 for (const id
of selectedRows
) {
1027 const infohash
= rows
[id
].full_data
.infohash_v1
;
1028 if (infohash
!== "")
1029 infohashes
.push(infohash
);
1033 for (const id
of selectedRows
) {
1034 const infohash
= rows
[id
].full_data
.infohash_v2
;
1035 if (infohash
!== "")
1036 infohashes
.push(infohash
);
1041 return infohashes
.join("\n");
1044 copyMagnetLinkFN = function() {
1045 const selectedRows
= torrentsTable
.selectedRowsIds();
1047 if (selectedRows
.length
> 0) {
1048 const rows
= torrentsTable
.getFilteredAndSortedRows();
1049 for (let i
= 0; i
< selectedRows
.length
; ++i
) {
1050 const hash
= selectedRows
[i
];
1051 magnets
.push(rows
[hash
].full_data
.magnet_uri
);
1054 return magnets
.join("\n");
1057 copyIdFN = function() {
1058 return torrentsTable
.selectedRowsIds().join("\n");
1061 copyCommentFN = function() {
1062 const selectedRows
= torrentsTable
.selectedRowsIds();
1063 const comments
= [];
1064 if (selectedRows
.length
> 0) {
1065 const rows
= torrentsTable
.getFilteredAndSortedRows();
1066 for (let i
= 0; i
< selectedRows
.length
; ++i
) {
1067 const hash
= selectedRows
[i
];
1068 const comment
= rows
[hash
].full_data
.comment
;
1069 if (comment
&& (comment
!== ""))
1070 comments
.push(comment
);
1073 return comments
.join("\n---------\n");
1076 exportTorrentFN
= async
function() {
1077 const hashes
= torrentsTable
.selectedRowsIds();
1078 for (const hash
of hashes
) {
1079 const row
= torrentsTable
.rows
.get(hash
);
1083 const name
= row
.full_data
.name
;
1084 const url
= new URI("api/v2/torrents/export");
1085 url
.setData("hash", hash
);
1087 // download response to file
1088 const element
= document
.createElement("a");
1090 element
.download
= (name
+ ".torrent");
1091 document
.body
.appendChild(element
);
1093 document
.body
.removeChild(element
);
1095 // https://stackoverflow.com/questions/53560991/automatic-file-downloads-limited-to-10-files-on-chrome-browser
1096 await window
.qBittorrent
.Misc
.sleep(200);
1100 addClickEvent("stopAll", (e
) => {
1102 e
.stopPropagation();
1104 if (confirm("QBT_TR(Would you like to stop all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1106 url
: "api/v2/torrents/stop",
1116 addClickEvent("startAll", (e
) => {
1118 e
.stopPropagation();
1120 if (confirm("QBT_TR(Would you like to start all torrents?)QBT_TR[CONTEXT=MainWindow]")) {
1122 url
: "api/v2/torrents/start",
1132 ["stop", "start", "recheck"].each((item
) => {
1133 addClickEvent(item
, (e
) => {
1135 e
.stopPropagation();
1137 const hashes
= torrentsTable
.selectedRowsIds();
1138 if (hashes
.length
) {
1139 hashes
.each((hash
, index
) => {
1141 url
: "api/v2/torrents/" + item
,
1153 ["decreasePrio", "increasePrio", "topPrio", "bottomPrio"].each((item
) => {
1154 addClickEvent(item
, (e
) => {
1156 e
.stopPropagation();
1157 setQueuePositionFN(item
);
1161 setQueuePositionFN = function(cmd
) {
1162 const hashes
= torrentsTable
.selectedRowsIds();
1163 if (hashes
.length
) {
1165 url
: "api/v2/torrents/" + cmd
,
1168 hashes
: hashes
.join("|")
1175 addClickEvent("about", (e
) => {
1177 e
.stopPropagation();
1179 const id
= "aboutpage";
1180 new MochaUI
.Window({
1182 icon
: "images/qbittorrent-tray.svg",
1183 title
: "QBT_TR(About qBittorrent)QBT_TR[CONTEXT=AboutDialog]",
1185 contentURL
: new URI("views/about.html").toString(),
1187 css
: ["css/Tabs.css"]
1190 toolbarURL
: "views/aboutToolbar.html",
1192 width
: loadWindowWidth(id
, 550),
1193 height
: loadWindowHeight(id
, 360),
1194 onResize: function() {
1200 addClickEvent("logout", (e
) => {
1202 e
.stopPropagation();
1205 url
: "api/v2/auth/logout",
1207 onSuccess: function() {
1208 window
.location
.reload(true);
1213 addClickEvent("shutdown", (e
) => {
1215 e
.stopPropagation();
1217 if (confirm("QBT_TR(Are you sure you want to quit qBittorrent?)QBT_TR[CONTEXT=MainWindow]")) {
1219 url
: "api/v2/app/shutdown",
1221 onSuccess: function() {
1222 const shutdownMessage
= "QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]".replace("%1", window
.qBittorrent
.Client
.mainTitle());
1223 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>`);
1226 window
.qBittorrent
.Client
.stop();
1232 // Deactivate menu header links
1233 $$("a.returnFalse").each((el
) => {
1234 el
.addEventListener("click", (e
) => {
1236 e
.stopPropagation();