├── app
├── img
│ ├── fb.png
│ ├── heart.png
│ ├── icon.icns
│ ├── icon.ico
│ ├── icon.png
│ ├── icon_16.png
│ ├── icon_32.png
│ ├── patreon.png
│ ├── splash.png
│ ├── twitter.png
│ ├── icon_noshadow_48.png
│ ├── tech_tac_alpha_127.png
│ ├── ob_text_logo_titlebar.png
│ ├── ui-icons_444444_256x240.png
│ ├── import.svg
│ ├── update_check.svg
│ ├── ball.svg
│ ├── dots.svg
│ ├── load.svg
│ └── update_trcks.svg
├── fonts
│ ├── woff
│ │ ├── XRXV3I6Li01BKofINeaB.woff2
│ │ ├── XRXW3I6Li01BKofAjsOUYevI.woff2
│ │ └── XRXW3I6Li01BKofAksCUYevI.woff2
│ └── font.css
├── data
│ ├── themes
│ │ ├── assets
│ │ │ ├── katdark
│ │ │ │ └── titlebar.png
│ │ │ ├── tpblight
│ │ │ │ ├── titlebar.png
│ │ │ │ └── custom.css
│ │ │ └── carbondark
│ │ │ │ └── custom.css
│ │ └── themes.db
│ └── config.db
├── renderers
│ └── RndSplash.js
├── css
│ ├── clusterize.css
│ ├── styles.css
│ ├── jquery-ui.min.css
│ ├── theme.css
│ ├── comp-override.css
│ └── WndMain.css
├── reset-db.js
├── js
│ ├── jquery.dragbetter.js
│ ├── WndMain.js
│ ├── clusterize.min.js
│ └── bootstrap-notify.min.js
├── main-functions
│ ├── scrape.js
│ ├── upd-dump.js
│ ├── deprecated
│ │ ├── import-dumo-method-2.js
│ │ └── import-dump-method-1.js
│ ├── search.js
│ └── import-dump.js
├── WndSplash.html
├── WndMain.html
└── main.js
├── changelog.txt
├── LICENSE
├── .gitignore
├── package.json
└── README.md
/app/img/fb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/fb.png
--------------------------------------------------------------------------------
/app/img/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/heart.png
--------------------------------------------------------------------------------
/app/img/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon.icns
--------------------------------------------------------------------------------
/app/img/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon.ico
--------------------------------------------------------------------------------
/app/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon.png
--------------------------------------------------------------------------------
/app/img/icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon_16.png
--------------------------------------------------------------------------------
/app/img/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon_32.png
--------------------------------------------------------------------------------
/app/img/patreon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/patreon.png
--------------------------------------------------------------------------------
/app/img/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/splash.png
--------------------------------------------------------------------------------
/app/img/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/twitter.png
--------------------------------------------------------------------------------
/app/img/icon_noshadow_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/icon_noshadow_48.png
--------------------------------------------------------------------------------
/app/img/tech_tac_alpha_127.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/tech_tac_alpha_127.png
--------------------------------------------------------------------------------
/app/img/ob_text_logo_titlebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/ob_text_logo_titlebar.png
--------------------------------------------------------------------------------
/app/img/ui-icons_444444_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/img/ui-icons_444444_256x240.png
--------------------------------------------------------------------------------
/app/fonts/woff/XRXV3I6Li01BKofINeaB.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/fonts/woff/XRXV3I6Li01BKofINeaB.woff2
--------------------------------------------------------------------------------
/app/data/themes/assets/katdark/titlebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/data/themes/assets/katdark/titlebar.png
--------------------------------------------------------------------------------
/app/data/themes/assets/tpblight/titlebar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/data/themes/assets/tpblight/titlebar.png
--------------------------------------------------------------------------------
/app/fonts/woff/XRXW3I6Li01BKofAjsOUYevI.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/fonts/woff/XRXW3I6Li01BKofAjsOUYevI.woff2
--------------------------------------------------------------------------------
/app/fonts/woff/XRXW3I6Li01BKofAksCUYevI.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m4heshd/offlinebay/HEAD/app/fonts/woff/XRXW3I6Li01BKofAksCUYevI.woff2
--------------------------------------------------------------------------------
/app/data/themes/assets/tpblight/custom.css:
--------------------------------------------------------------------------------
1 | .tbl-grid, .trackers-tbl {
2 | background: #f6f1ee;
3 | }
4 |
5 | .table-striped tbody tr:nth-of-type(odd) {
6 | background-color: #f2ece7;
7 | }
8 |
9 | thead {
10 | background: #d2b9a6;
11 | }
12 |
13 | .table > thead > tr > th {
14 | border-top: none;
15 | }
--------------------------------------------------------------------------------
/app/renderers/RndSplash.js:
--------------------------------------------------------------------------------
1 | const {ipcRenderer} = require('electron');
2 |
3 | window.onerror = function (msg, url, lineNo, columnNo, error) {
4 | ipcRenderer.send('logger', '[SPLASH]' + error.stack);
5 | }; // Send window errors to Main process
6 |
7 | ipcRenderer.on('fade', function () {
8 | document.body.style.opacity = '1';
9 | });
--------------------------------------------------------------------------------
/changelog.txt:
--------------------------------------------------------------------------------
1 | v 2.0.0:
2 | - Programming language switched to Javascript (NodeJS) from Java
3 | - Functionality stabilized on all platforms
4 | - GUI Built on Electron framework (HTML + CSS + Js)
5 | - Splash screen
6 | - Smoother animations
7 | - DB switched to NoSQL (NeDB)
8 | - Made Open-source
9 | - Gzip dump import support
10 | - 85% Faster importing
11 | - Drag and Drop importing
12 | - Remote dump update support
13 | - Auto dump update support
14 | - System notifications support
15 | - System tray support
16 | - 15% Faster searching
17 | - Instant search feature
18 | - Search autocomplete support
19 | - Fixed table sorting
20 | - Faster filtering
21 | - Faster Seeds/Peers discovery
22 | - DHT support for scraping
23 | - Customizable tracker fetching URL
24 | - Best trackers selection for magnet links
25 | - Theming support
26 | - Customizable configurations (Preferences window)
27 | - Configurations import/export support
28 | - Google search button
29 | - Windows shortcut helper
30 | - Logging to file support
31 |
32 | v 1.0.2:
33 | - Initial Release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Mahesh Bandara Wijerathna (m4heshd)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/css/clusterize.css:
--------------------------------------------------------------------------------
1 | /* max-height - the only parameter in this file that needs to be edited.
2 | * Change it to suit your needs. The rest is recommended to leave as is.
3 | */
4 | .clusterize-scroll{
5 | max-height: 2160px;
6 | /*overflow: auto;*/
7 | }
8 |
9 | /**
10 | * Avoid vertical margins for extra tags
11 | * Necessary for correct calculations when rows have nonzero vertical margins
12 | */
13 | .clusterize-extra-row{
14 | margin-top: 0 !important;
15 | margin-bottom: 0 !important;
16 | }
17 |
18 | /* By default extra tag .clusterize-keep-parity added to keep parity of rows.
19 | * Useful when used :nth-child(even/odd)
20 | */
21 | .clusterize-extra-row.clusterize-keep-parity{
22 | display: none;
23 | }
24 |
25 | /* During initialization clusterize adds tabindex to force the browser to keep focus
26 | * on the scrolling list, see issue #11
27 | * Outline removes default browser's borders for focused elements.
28 | */
29 | .clusterize-content{
30 | outline: 0;
31 | counter-reset: clusterize-counter;
32 | }
33 |
34 | /* Centering message that appears when no data provided
35 | */
36 | .clusterize-no-data td{
37 | text-align: center;
38 | }
--------------------------------------------------------------------------------
/app/fonts/font.css:
--------------------------------------------------------------------------------
1 | /* latin */
2 | @font-face {
3 | font-family: 'Nunito';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: local('Nunito Regular'), local('Nunito-Regular'), url(woff/XRXV3I6Li01BKofINeaB.woff2) format('woff2');
7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
8 | }
9 | @font-face {
10 | font-family: 'Nunito';
11 | font-style: normal;
12 | font-weight: 700;
13 | src: local('Nunito Bold'), local('Nunito-Bold'), url(woff/XRXW3I6Li01BKofAjsOUYevI.woff2) format('woff2');
14 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
15 | }
16 | @font-face {
17 | font-family: 'Nunito';
18 | font-style: normal;
19 | font-weight: 800;
20 | src: local('Nunito ExtraBold'), local('Nunito-ExtraBold'), url(woff/XRXW3I6Li01BKofAksCUYevI.woff2) format('woff2');
21 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
22 | }
--------------------------------------------------------------------------------
/app/img/import.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/img/update_check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dump files
2 | *.csv
3 | app/data/downloads/
4 |
5 | # Distribution
6 | dist
7 |
8 | # Logs
9 | logs
10 | *.log
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 | *.pid.lock
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Typescript v1 declaration files
47 | typings/
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional eslint cache
53 | .eslintcache
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # dotenv environment variables file
65 | .env
66 |
67 | # next.js build output
68 | .next
69 |
70 | # Editor directories and files
71 | .idea
72 | .vscode
73 | *.suo
74 | *.ntvs*
75 | *.njsproj
76 | *.sln
77 | *.sw?
78 |
--------------------------------------------------------------------------------
/app/css/styles.css:
--------------------------------------------------------------------------------
1 | /* Global
2 | --------------*/
3 | :root{
4 | --txtclr : #8a9ba1;
5 | }
6 | body {
7 | font-family: Roboto,sans-serif;
8 | font-size: 13px;
9 | line-height: 1.42857143;
10 | color: #8a9ba1;
11 | background-color: #1e2a31;
12 | }
13 |
14 | /* Global Components
15 | ----------------------*/
16 |
17 | .txtbox {
18 | border: 0;
19 | height: inherit;
20 | padding: 10px 20px 10px 50px;
21 | border-radius: 2px;
22 | width: 50%;
23 | background-color: #2b3942;
24 | font-size: 15px;
25 | color: var(--txtclr);
26 | }
27 |
28 | .btn-ui{
29 | border: 0;
30 | height: inherit;
31 | padding: 10px;
32 | border-radius: 4px;
33 | background-color: #2b3942;
34 | font-size: 15px;
35 | color: var(--txtclr);
36 | }
37 |
38 | .txtbox:focus {
39 | background: #edeff0;
40 | color: #22313a;
41 | }
42 |
43 | ::placeholder {
44 | color: var(--txtclr);
45 | opacity: 1;
46 | }
47 |
48 | *,.txtbox, .btn-ui {
49 | -webkit-transition: all;
50 | -o-transition: all;
51 | transition: all;
52 | -webkit-transition-duration: .3s;
53 | transition-duration: .3s;
54 | }
55 |
56 | .top-search__reset {
57 | left: 0;
58 | font-size: 20px;
59 | color: #b4bfc3;
60 | top: 0;
61 | position: absolute;
62 | cursor: pointer;
63 | width: 50px;
64 | height: 100%;
65 | }
66 |
67 | .top-search__reset:hover {
68 | opacity: .75;
69 | filter: alpha(opacity=75);
70 | }
--------------------------------------------------------------------------------
/app/img/ball.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/data/config.db:
--------------------------------------------------------------------------------
1 | {"type":"win-state","maxed":false,"position":[null,null],"size":[1280,800],"_id":"0"}
2 | {"type":"trackers","url":"https://newtrackon.com/api/stable","trackers":["http://tracker.tfile.me:80/announce","udp://tracker.torrent.eu.org:451/announce","udp://thetracker.org:80/announce","http://torrent.nwps.ws:80/announce","http://alpha.torrenttracker.nl:443/announce","http://0d.kebhana.mx:443/announce","udp://bt.xxx-tracker.com:2710/announce","udp://inferno.demonoid.pw:3418/announce","udp://tracker.justseed.it:1337/announce","udp://tracker.vanitycore.co:6969/announce","udp://open.stealth.si:80/announce","udp://tracker.coppersurfer.tk:6969/announce","udp://tracker.opentrackr.org:1337/announce","udp://tracker.cypherpunks.ru:6969/announce","udp://tracker.tiny-vps.com:6969/announce","http://retracker.mgts.by:80/announce","udp://retracker.lanta-net.ru:2710/announce","udp://public.popcorn-tracker.org:6969/announce","http://tracker.skyts.net:6969/announce","udp://ipv4.tracker.harry.lu:80/announce","http://tracker.city9x.com:2710/announce","udp://tracker.martlet.tk:6969/announce","http://retracker.telecom.by:80/announce","udp://9.rarbg.com:2710/announce"],"_id":"1"}
3 | {"type":"search","rs_count":100,"smart":true,"inst":false,"useAC":true,"history":[],"_id":"2"}
4 | {"type":"dump","updURL":"https://thepiratebay.org/static/dump/csv/torrent_dump_full.csv.gz","updLast":"2003-01-01T00:00:00.000Z","updType":"notify","updInt":10,"keepDL":true,"updStat":["complete","",""],"_id":"3"}
5 | {"type":"gen","sysTray":true,"logToFile":true,"useDHT":true,"supMsg":"2018-06-01T05:21:44.285Z","confVer":"1.0","_id":"4"}
6 | {"type":"etc","thmURL":"https://github.com/m4heshd/offlinebay-themes","appUpdURL":"https://raw.githubusercontent.com/m4heshd/ob_release/master/release.json","_id":"5"}
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "offlinebay",
3 | "version": "2.0.0",
4 | "description": "Offline version of The Pirate Bay",
5 | "main": "app/main.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "clean": "node app/reset-db.js",
10 | "dist-mac": "electron-packager . OfflineBay --overwrite --platform=darwin --arch=x64 --icon=app/img/icon.icns --app-bundle-id=com.m4heshd.offlinebay --prune=true --out=dist --app-copyright=\"Copyright (C) 2018 m4heshd\"",
11 | "dist-win": "electron-packager . OfflineBay --overwrite --platform=win32 --arch=ia32 --icon=app/img/icon.ico --prune=true --out=dist --win32metadata.FileDescription=\"OfflineBay\" --win32metadata.ProductName=\"OfflineBay by m4heshd\" --app-copyright=\"Copyright (C) 2018 m4heshd\"",
12 | "dist-linux-64": "electron-packager . OfflineBay --overwrite --platform=linux --arch=x64 --icon=app/img/icon.png --prune=true --out=dist --app-copyright=\"Copyright (C) 2018 m4heshd\"",
13 | "dist-linux-32": "electron-packager . OfflineBay --overwrite --platform=linux --arch=ia32 --icon=app/img/icon.png --prune=true --out=dist --app-copyright=\"Copyright (C) 2018 m4heshd\""
14 | },
15 | "author": "m4heshd",
16 | "license": "MIT",
17 | "dependencies": {
18 | "adm-zip": "0.4.9",
19 | "bittorrent-dht": "8.2.0",
20 | "bittorrent-tracker": "9.7.0",
21 | "bootstrap": "4.0.0",
22 | "jquery": "3.3.1",
23 | "material-design-iconic-font": "2.2.0",
24 | "moment": "2.22.1",
25 | "nedb": "1.8.0",
26 | "papaparse": "4.3.7",
27 | "popper.js": "1.14.1",
28 | "request": "2.85.0",
29 | "rimraf": "2.6.2"
30 | },
31 | "devDependencies": {
32 | "electron": "1.8.4",
33 | "electron-packager": "12.1.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/data/themes/assets/carbondark/custom.css:
--------------------------------------------------------------------------------
1 | body, .modal-content {
2 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAABGCAAAAABURb1YAAAFnklEQVR4AWWXCY4cwXbE6h4JMhCvMPc/ov+3Ox9QsLCGpKlFHILd+UQ559AGDkBSFXWmomonqkL/XuUcIPUcONjHCJyE36KaqNiaRnVCG9TGpgIU9hIfohCBVGj24ammo04lU3Fy0NZDAyZi5AElHjgEGlRbG1WjraIt7TmADQkAaoEHsOXAAaeqat9RVTqjosyEH5W+DQCQt8BzIJMfXJI3e5tGRefSlRn3NrO3scnvNl26FN5RITGNfjm3GM/+FhQgnlQeImC+dEm8rzHVTNGL9EADVsTAIfGJBziRy7RlmcZU1IYpHIj+4JrWym8/XSwzompmFNVOFdTOAAfs61KZ6eXcp57fykQUOxNBmTesz2HpCnCg3UV/bA7VRpWGNqKtrYLTs0ilCjgxiZxjeLC5dDPBFn4Pn2ja6BRUL10DTgET4Focq8rn8casxV4E5tIlZvytqtfivCOKZrqc38G7egnkXecyPZdzubc57VtUnbfL+a0u3b1N3d9Mci7cKocHoh3xihLQ6fXUlgTOOfVkXSUFINoWHhqxE23RTmCqtoEJaITkIrUCkU9fnqpqm+40VSXS3CLaSzfGBb4rPuOSfN0ylLve6/jpcrbL2dTlnOdv5AamKjLvLN1XruMNS/cul/hx8twKpKYRaE2DOp8+c+lW7uVXVQ7Vx0x1G3HXjNocsHCac7NbWc6egwJVHu2kRTVNC2pqv2U4XNsv5/yWJlV9+NJ1C7xluInlcr7rK+Kg+qy7onQqgnbi7UHcS3qXjdw3bO5tumycYlVssMI5EXXpRnatPK362KodtVU60cm+eQT10sVLFwJYlVaf7UGSoi5dQF3DvnTX3UvXTPO8quKH7jsXhm/PxfKWZXqBM5sS33kmKtC31Q05fI095i5IPpyzRXqSy1Sr17p1F7NE8vMtnNbLpVVtk8c0OrfF0sKxHnIpYgHW4puMomTz8mgmrSjpvrxhX97l7LqLpl7bW9BH9/MNzF8XwRqL8/m7c30eFYV5VX3QTquK06XbLt16F4ks+8Z9m6gPtpcuk3OvibCekng4lzMHi9MoqZmKj9tdbQCbc7bcEA+oEJduRNNL1waf1tuI9MDBUK8t3k9uFz3rrqadO9Ln9TL/ezlwDvky3TW7ZmXfpc77TN0yhOXnP939LvVDV9s+tgLTm0TqYd1lea3PRFuVjLa562HdPbCfascGrvO3DMeIATVqVRO8vfHrbupdfo1dzlXUdkVLZsS1mLPG3rWcl+4B1l1537h0VXzU1tyLG7cCu+LSrSqo06hocy3WCcil65qHDbuEYzQN2HpT3jFt9LEFvHTP1nxbXOEkbhkKmd+6BTfVZ/I143z/Fyz6Za92o0I7I6gzfeYAHPwy/Rb43L9TLtMt8CtwW1xvQvbR1g9n7qJRNN1kdxcdH7cM58Szxq679MdLOhE72t6VNEpHHkzwRyjBcGth+dE99pIkVbWaEWmvnQ8Yl7Mp59zpRWXdNo6odrO754Zzvxf/010nP7onb1TR+bgbVPB9PcADJvH/043Kvk0UzLRxQ6UiaeTAcyxYl2lyrrvZv8v2wE7U1uVcST0821jJeIDKWuyxiqnuko7q3+zJYXJ46nV3D67EyL+lQ9F2qkr+3kUafQpwYE8T329p6y7OX1TF97fQv/c+l87zj7Hsgpig4qRLd9fMPs74rLGyCzRbhmVqpy7nqvPmTD1gvKe7b2U8u1QzmtuDuEtbgT3c8QBJveo6cueFkUwv59nVmfFw1jl4/unuOT9qERV76ers8n23z/c8/Xy7a4lwwLplmGusbRp33Up13+ZDN3KPxNVMhY62vUQyRTse6+cKHtZYjIfDh260Cjbb3Vy6R8H8SMBzqSwrsB1UTacoknlnOb8V4JD9Njfpwz/fHjpBUeevcAtcl65sqXPX9D87RUTLc/dfOgAAAABJRU5ErkJggg==);
3 | }
--------------------------------------------------------------------------------
/app/css/jquery-ui.min.css:
--------------------------------------------------------------------------------
1 | /*! jQuery UI - v1.12.1 - 2018-04-21
2 | * http://jqueryui.com
3 | * Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css
4 | * Copyright jQuery Foundation and other contributors; Licensed MIT */
5 |
6 | .ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}
--------------------------------------------------------------------------------
/app/reset-db.js:
--------------------------------------------------------------------------------
1 | console.log('Resetting config.db to initial state......');
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | let records = '{"type":"win-state","maxed":false,"position":[null,null],"size":[1280,800],"_id":"0"}\n' +
7 | '{"type":"trackers","url":"https://newtrackon.com/api/stable","trackers":["http://tracker.tfile.me:80/announce","udp://tracker.torrent.eu.org:451/announce","udp://thetracker.org:80/announce","http://torrent.nwps.ws:80/announce","http://alpha.torrenttracker.nl:443/announce","http://0d.kebhana.mx:443/announce","udp://bt.xxx-tracker.com:2710/announce","udp://inferno.demonoid.pw:3418/announce","udp://tracker.justseed.it:1337/announce","udp://tracker.vanitycore.co:6969/announce","udp://open.stealth.si:80/announce","udp://tracker.coppersurfer.tk:6969/announce","udp://tracker.opentrackr.org:1337/announce","udp://tracker.cypherpunks.ru:6969/announce","udp://tracker.tiny-vps.com:6969/announce","http://retracker.mgts.by:80/announce","udp://retracker.lanta-net.ru:2710/announce","udp://public.popcorn-tracker.org:6969/announce","http://tracker.skyts.net:6969/announce","udp://ipv4.tracker.harry.lu:80/announce","http://tracker.city9x.com:2710/announce","udp://tracker.martlet.tk:6969/announce","http://retracker.telecom.by:80/announce","udp://9.rarbg.com:2710/announce"],"_id":"1"}\n' +
8 | '{"type":"search","rs_count":100,"smart":true,"inst":false,"useAC":true,"history":[],"_id":"2"}\n' +
9 | '{"type":"dump","updURL":"https://thepiratebay.org/static/dump/csv/torrent_dump_full.csv.gz","updLast":"2003-01-01T00:00:00.000Z","updType":"notify","updInt":10,"keepDL":true,"updStat":["complete","",""],"_id":"3"}\n' +
10 | '{"type":"gen","sysTray":true,"logToFile":true,"useDHT":true,"supMsg":"' + new Date().toISOString() +'","confVer":"1.0","_id":"4"}\n' +
11 | '{"type":"etc","thmURL":"https://github.com/m4heshd/offlinebay-themes","appUpdURL":"https://raw.githubusercontent.com/m4heshd/ob_release/master/release.json","_id":"5"}\n';
12 |
13 | fs.writeFile(path.join(__dirname, 'data', 'config.db'), records, function (err) {
14 | err ? console.error(err + '\nFailed to reset the DB\n') : console.log('Task completed successfully');
15 | });
16 |
--------------------------------------------------------------------------------
/app/css/theme.css:
--------------------------------------------------------------------------------
1 | /* Theme color variables
2 | -------------------------*/
3 | :root{
4 | --bodyTxt : #b4bfc3;
5 | --bodyBg : #1e2a31;
6 | --compClr : #2b3942;
7 | --compShadow1 : rgba(0,0,0,0.16);
8 | --compShadow2 : rgba(0,0,0,0.08);
9 | --txtFocusBg : #edeff0;
10 | --txtFocustxt : #22313a;
11 | --txtFocusPH : #354c5a;
12 | --btnTxtClr : #b4bfc3;
13 | --btnTxtHover : #fff;
14 | --btnBgHover : #138496;
15 | --btnFocusGlow : rgba(23, 162, 184, 0.5);
16 | --btnActive : #117a8b;
17 | --btnTxtDisable : #b4bfc3;
18 | --btnBgDisable : #4e4e4e;
19 | --btnBorderDisable : #17a2b8;
20 | --chkChecked : #edeff0;
21 | --chkUnchecked : rgba(177, 188, 192, 0.47);
22 | --tblHeadBottomBorder : #b4bfc3;
23 | --tblHeadHover: rgba(255, 255, 255, 0.01);
24 | --tblCellBorder : #30414a;
25 | --tblActiveRow : #22313a;
26 | --tblActiveRowHover: #19232a;
27 | --scrollBg : #24303a;
28 | --scrollBorder : #252525;
29 | --scrollThumb : #35464c;
30 | --mnuBtnBg: rgba(0, 0, 0, 0);
31 | --mnuBg: #edeff0;
32 | --mnuTxt: #4a606d;
33 | --mnuItemHover: rgba(0,0,0,.04);
34 | --mnuGrade1: rgba(255,0,0,0);
35 | --mnuGrade2: rgb(43, 57, 66);
36 | --mnuGlow: rgba(0,0,0,.175);
37 | --modalGlow: rgba(0,0,0,.5);
38 | --overlay: rgba(0,0,0,0.6);
39 | --olEffect: blur(2px);
40 | --olTxt: #b4bfc3;
41 | }
--------------------------------------------------------------------------------
/app/js/jquery.dragbetter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jquery.dragbetter 0.1.3
3 | *
4 | * Author: Andrey Mikhaylov aka lolmaus
5 | * Email: lolmaus@gmail.com
6 | *
7 | * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
8 | *
9 | * Based on jquery.draghover.js by William Meleyal (william.meleyal@gmail.com): https://gist.github.com/meleyal/3794126.
10 | *
11 | * Inspired by jquery.event.dragout by Dan Cork ([Firstname].[Lastname]@kickinteractive.net): https://github.com/dancork/jquery.event.dragout .
12 | *
13 | * Thanks to Ian Bytchek for support.
14 | *
15 | */
16 |
17 | ;(function($){
18 |
19 | "use strict";
20 |
21 | $.event.special.dragbetterenter = {
22 |
23 | setup: function() {
24 |
25 | var
26 | self = this,
27 | $self = $(self);
28 |
29 | self.dragbetterCollection = [];
30 |
31 | $self.on('dragenter.dragbetterenter', function (event) {
32 |
33 | if (self.dragbetterCollection.length === 0) {
34 | $self.triggerHandler('dragbetterenter');
35 | }
36 |
37 | self.dragbetterCollection.push(event.target);
38 | });
39 |
40 | $self.on('drop.dragbetterenter dragend.dragbetterenter', function () {
41 | self.dragbetterCollection = [];
42 | $self.triggerHandler('dragbetterleave');
43 | });
44 | },
45 |
46 | teardown: function() {
47 | $(this).off('.dragbetterenter');
48 | }
49 |
50 | };
51 |
52 | $.event.special.dragbetterleave = {
53 |
54 | setup: function() {
55 |
56 | if (!this.dragbetterCollection)
57 | throw "Triggered dragbetterleave without dragbetterenter. Do you listen to dragbetterenter?";
58 |
59 | var
60 | self = this,
61 | $self = $(self);
62 |
63 |
64 | $self.on('dragleave.dragbetterleave', function (event) {
65 |
66 | // Timeout is needed to ensure that the leave event on the previous element
67 | // fires AFTER the enter event on the next element.
68 | setTimeout(function() {
69 |
70 | var currentElementIndex = self.dragbetterCollection.indexOf(event.target);
71 | if (currentElementIndex > -1)
72 | self.dragbetterCollection.splice(currentElementIndex, 1);
73 |
74 | if (self.dragbetterCollection.length === 0) {
75 | $self.triggerHandler('dragbetterleave');
76 | }
77 | }, 1);
78 | });
79 | },
80 |
81 | teardown: function() {
82 | $(this).off('.dragbetterleave');
83 | }
84 |
85 | };
86 |
87 | })(window.jQuery);
88 |
--------------------------------------------------------------------------------
/app/img/dots.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/main-functions/scrape.js:
--------------------------------------------------------------------------------
1 | const Tracker = require('bittorrent-tracker');
2 | const DHT = require('bittorrent-dht');
3 | const path = require('path');
4 | const Datastore = require('nedb');
5 |
6 | process.on('uncaughtException', function (error) {
7 | console.log(error);
8 | process.send(['scrape-failed', 'general']); //mainWindow.webContents.send('scrape-failed', 'general');
9 | });
10 |
11 | let args = process.argv.slice(2);
12 | let hash = args[0];
13 | let isDHT = (args[1] === 'true');
14 | let trackers = [];
15 |
16 | let config = new Datastore({
17 | filename: path.join(process.cwd(), 'data', 'config.db'),
18 | autoload: true
19 | });
20 |
21 | getTrackers().then(function (trcks) {
22 | if (!trcks || !trcks.length) {
23 | process.send(['scrape-warn', 'empty']); //mainWindow.webContents.send('scrape-warn', 'empty');
24 | } else {
25 | trackers = trcks;
26 | scrapeTrackers();
27 | }
28 | if (isDHT) {
29 | scrapeDHT();
30 | }
31 | }).catch(function (err) {
32 | process.send(['scrape-warn', 'db']); //mainWindow.webContents.send('scrape-warn', 'db');
33 | console.log(err);
34 | if (isDHT) {
35 | scrapeDHT();
36 | }
37 | });
38 |
39 | // Get trackers list from DB
40 | function getTrackers() {
41 | return new Promise((resolve, reject) => {
42 | config.findOne({type: 'trackers'}, function (err, trck) {
43 | if (!err && trck) {
44 | resolve(trck.trackers);
45 | } else {
46 | console.log(err);
47 | reject();
48 | }
49 | })
50 | });
51 | }
52 |
53 | function scrapeDHT() {
54 | let dht = new DHT();
55 | let timer;
56 |
57 | dht.listen(20000);
58 |
59 | dht.on('peer', function () {
60 | if (timer){
61 | process.send(['scrape-update-DHT', 0]); //mainWindow.webContents.send('scrape-update-DHT');
62 | clearTimeout(timer);
63 | timer = setTimeout(function () {
64 | dht.destroy();
65 | }, 5000);
66 | }
67 | });
68 |
69 | dht.lookup(hash);
70 |
71 | timer = setTimeout(function () {
72 | dht.destroy();
73 | }, 5000);
74 |
75 | }
76 |
77 | function scrapeTrackers() {
78 | let opts = {
79 | infoHash: hash,
80 | announce: trackers,
81 | peerId: new Buffer('01234567890123456789'),
82 | port: 6881
83 | };
84 |
85 | let client = new Tracker(opts);
86 |
87 | client.scrape();
88 |
89 | client.on('scrape', function (data) {
90 | process.send(['scrape-update', data]); //mainWindow.webContents.send('scrape-update', data);
91 | }).on('error', function (err) {
92 | console.log(err);
93 | });
94 | }
--------------------------------------------------------------------------------
/app/js/WndMain.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function () {
2 |
3 | /* Search bar animations
4 | ----------------------------*/
5 | $(".top-search__input").on("focus", function () {
6 | $(".top-search").addClass("top-search--focused");
7 | }).on("blur", function () {
8 | let a = $(this).val();
9 | !a.length > 0 && $(".top-search").removeClass("top-search--focused");
10 | });
11 | $(".top-search").on("click", ".top-search__reset", function () {
12 | $(".top-search").removeClass("top-search--focused");
13 | $(".top-search__input").val("");
14 | });
15 |
16 | /* Menu dropdown animations
17 | ----------------------------*/
18 | $('.dropdown').on('show.bs.dropdown', function () {
19 | $(this).find('.dropdown-menu').first().stop(true, true).css({
20 | opacity: 1,
21 | transition: 'opacity 0.2s'
22 | }).slideDown(200);
23 | }).on('hide.bs.dropdown', function () {
24 | $(this).find('.dropdown-menu').first().stop(true, true).css({
25 | opacity: 0,
26 | transition: 'opacity 0.2s'
27 | }).slideUp(200);
28 | });
29 |
30 | /* Modals
31 | --------------------*/
32 | /* Blur effect */
33 | let mdls = $('.modal');
34 | mdls.on('show.bs.modal', function () {
35 | $('.body-container').css('filter', 'var(--olEffect)');
36 | });
37 | mdls.on('hide.bs.modal', function () {
38 | $('.body-container').css('filter', '');
39 | });
40 | /* Draggable Modal */
41 | $('.mdl-drag .modal-content').resizable({
42 | //alsoResize: ".modal-dialog",
43 | minHeight: 300,
44 | minWidth: 300
45 | });
46 | $('.mdl-drag').draggable({
47 | handle: '.modal-titlebar'
48 | });
49 |
50 | /* Data table functions
51 | ----------------------------*/
52 | /* tblMain */
53 | $("#tblMainBody").on('click', 'tr', function () {
54 | if (!$(this).hasClass('active')) {
55 | $('#tblMain .active').removeClass('active');
56 | $(this).addClass('active');
57 | $('.imgDots').css({
58 | visibility: 'hidden'
59 | });
60 | $('#pnlSeeds').css({
61 | visibility: 'hidden'
62 | });
63 | }
64 | });
65 | function resizeHeader() {
66 | $('th[data-type="date"]').width($('#hdrDate').width());
67 | $('th[data-type="name"]').width($('#hdrName').width());
68 | $('th[data-type="size"]').width($('#hdrSize').width());
69 | $('#tblMain').css('margin-top', '-' + $('#hdrDate').css('height'));
70 | }
71 | new ResizeObserver(resizeHeader).observe(hdrDate);
72 |
73 | /* tblTrackers */
74 | $("#tblTrackers").on('click', 'tr', function () {
75 | if (!$(this).hasClass('active')) {
76 | $('#tblTrackers .active').removeClass('active');
77 | $(this).addClass('active');
78 | }
79 | });
80 |
81 | });
--------------------------------------------------------------------------------
/app/img/load.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/main-functions/upd-dump.js:
--------------------------------------------------------------------------------
1 | const request = require('request');
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | // Keep the application from crashing on unexpected errors
6 | process.on('uncaughtException', function (error) {
7 | console.log(error);
8 | process.send(['upd-dump-failed', ['general', type]]); //mainWindow.webContents.send('upd-dump-failed', ['general', type]);
9 | });
10 |
11 | let args = process.argv.slice(2);
12 | let type = args[0];
13 | let down_url = args[1];
14 | let targetPath = path.join(process.cwd(), 'data', 'downloads', 'torrent_dump_full.csv.gz');
15 | let tstamp = new Date().toString();
16 |
17 | // Download the dump file from provided URL
18 | function downloadDump(down_url, targetPath) {
19 | let received_bytes = 0;
20 | let total_bytes = 0;
21 | let goodFile = true;
22 |
23 | ensureDlDir(targetPath);
24 |
25 | let out = fs.createWriteStream(targetPath)
26 | .on('error', function (err) {
27 | console.log(err);
28 | process.send(['upd-dump-failed', ['file', type]]); //mainWindow.webContents.send('upd-dump-failed', ['file', type]);
29 | })
30 | .on('open', function () {
31 |
32 | let req = request({
33 | method: 'GET',
34 | uri: down_url
35 | });
36 |
37 | req.pipe(out);
38 |
39 | req.on('response', function (data) {
40 | if((data.headers['content-type'].split('/')[0]) !== 'application'){
41 | process.send(['upd-dump-failed', ['content', type]]); //mainWindow.webContents.send('upd-dump-failed', ['content', type]);
42 | goodFile = false;
43 | req.abort();
44 | }
45 | tstamp = data.headers['last-modified'];
46 | total_bytes = parseInt(data.headers['content-length']);
47 | });
48 | req.on('data', function (chunk) {
49 | received_bytes += chunk.length;
50 | let progress = Math.round((received_bytes * 100) / total_bytes);
51 | process.send(['upd-dump-update', [progress, type]]); //mainWindow.webContents.send('upd-dump-update', [progress, type]);
52 | });
53 | req.on('error', function (err) {
54 | console.log(err);
55 | process.send(['upd-dump-failed', ['download', type]]); //mainWindow.webContents.send('upd-dump-failed', ['download', type]);
56 | });
57 |
58 | })
59 | .on('finish', function () {
60 | out.close();
61 | if (goodFile) {
62 | let final = fs.openSync(targetPath, 'r+');
63 | let dTstamp = new Date(tstamp);
64 | fs.futimesSync(final, dTstamp, dTstamp); // Set modified time of the downloaded file to the time from remote file
65 | fs.fsyncSync(final); // This part is essential to ensure that file is completely written to the disk before extracting
66 | fs.closeSync(final);
67 | process.send(['upd-dump-success', [targetPath, tstamp]]); //mainWindow.webContents.send('upd-dump-success', [targetPath, tstamp]);
68 | process.exit(0);
69 | }
70 | });
71 | }
72 |
73 | // Make sure the download directory is available before downloading
74 | function ensureDlDir(filePath) {
75 | let dirname = path.dirname(filePath);
76 | if (fs.existsSync(dirname)) {
77 | return true;
78 | }
79 | ensureDlDir(dirname);
80 | fs.mkdirSync(dirname);
81 | }
82 |
83 | downloadDump(down_url, targetPath);
--------------------------------------------------------------------------------
/app/WndSplash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/app/data/themes/themes.db:
--------------------------------------------------------------------------------
1 | {"name":"default","title":"Default","applied":true,"palette":{"bodyTxt":"#b4bfc3","bodyBg":"#1e2a31","compClr":"#2b3942","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#edeff0","txtFocustxt":"#22313a","txtFocusPH":"#354c5a","btnTxtClr":"#b4bfc3","btnTxtHover":"#fff","btnBgHover":"#138496","btnFocusGlow":"rgba(23, 162, 184, 0.5)","btnActive":"#117a8b","btnTxtDisable":"#b4bfc3","btnBgDisable":"#4e4e4e","btnBorderDisable":"#17a2b8","chkChecked":"#edeff0","chkUnchecked":"rgba(177, 188, 192, 0.47)","tblHeadBottomBorder":"#b4bfc3","tblHeadHover":"rgba(255, 255, 255, 0.01)","tblCellBorder":"#30414a","tblActiveRow":"#22313a","tblActiveRowHover":"#19232a","scrollBg":"#24303a","scrollBorder":"#252525","scrollThumb":"#35464c","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#edeff0","mnuTxt":"#4a606d","mnuItemHover":"rgba(0,0,0,.04)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"rgb(43, 57, 66)","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#b4bfc3"},"_id":"0"}
2 | {"name":"katdark","title":"KAT Dark","applied":false,"palette":{"bodyTxt":"#ffdd7d","bodyBg":"#594c2d","compClr":"#252525","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#ffdd7d","txtFocustxt":"#252525","txtFocusPH":"#354c5a","btnTxtClr":"#ffdd7d","btnTxtHover":"#252525","btnBgHover":"#ffdd7d","btnFocusGlow":"rgba(255, 221, 125, 0.41)","btnActive":"#c1a556","btnTxtDisable":"#ffffff","btnBgDisable":"#4e4e4e","btnBorderDisable":"#17a2b8","chkChecked":"#ffdd7d","chkUnchecked":"#ffdd7d","tblHeadBottomBorder":"#ffdd7d","tblHeadHover":"rgba(255, 255, 255, 0.01)","tblCellBorder":"#594c2d","tblActiveRow":"#332d1f","tblActiveRowHover":"#403723","scrollBg":"#252525","scrollBorder":"#252525","scrollThumb":"#8c7a48","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#ffdd7d","mnuTxt":"#252525","mnuItemHover":"rgba(0,0,0,.04)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"#252525","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#ffdd7d"},"_id":"0ahqLqhTDi7k0dWe"}
3 | {"name":"carbondark","title":"Carbon Dark","applied":false,"palette":{"bodyTxt":"#b4bfc3","bodyBg":"#1b1b1b","compClr":"#333333","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#171717","txtFocustxt":"#b4bfc3","txtFocusPH":"#9c5511","btnTxtClr":"#b4bfc3","btnTxtHover":"#fff","btnBgHover":"#9c5511","btnFocusGlow":"rgba(227, 124, 26, 0.12)","btnActive":"#633408","btnTxtDisable":"#b4bfc3","btnBgDisable":"#420e0e","btnBorderDisable":"#420e0e","chkChecked":"#edeff0","chkUnchecked":"rgba(177, 188, 192, 0.47)","tblHeadBottomBorder":"#b4bfc3","tblHeadHover":"rgba(255, 255, 255, 0.08)","tblCellBorder":"#252525","tblActiveRow":"#292929","tblActiveRowHover":"#1e1e1e","scrollBg":"#333333","scrollBorder":"#252525","scrollThumb":"#252525","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#edeff0","mnuTxt":"#1e1e1e","mnuItemHover":"rgba(0, 0, 0, 0.1)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"rgba(0, 0, 0, 0)","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#b4bfc3"},"_id":"Yf7czmuw5UoiUuKD"}
4 | {"name":"tpblight","title":"TPB Light","applied":false,"palette":{"bodyTxt":"#000000","bodyBg":"#e8e8e8","compClr":"#9e6943","compShadow1":"rgba(0,0,0,0.16)","compShadow2":"rgba(0,0,0,0.08)","txtFocusBg":"#ffffff","txtFocustxt":"#000000","txtFocusPH":"#354c5a","btnTxtClr":"#000000","btnTxtHover":"#fff","btnBgHover":"#b97f55","btnFocusGlow":"rgba(132, 57, 4, 0.39)","btnActive":"#865838","btnTxtDisable":"#b4bfc3","btnBgDisable":"#4e4e4e","btnBorderDisable":"#17a2b8","chkChecked":"#9e6943","chkUnchecked":"#9e6943","tblHeadBottomBorder":"#000000","tblHeadHover":"#b97f55","tblCellBorder":"#ffffff","tblActiveRow":"#d2b9a6","tblActiveRowHover":"#b97f55","scrollBg":"#b9a495","scrollBorder":"#252525","scrollThumb":"#9e6943","mnuBtnBg":"rgba(0, 0, 0, 0)","mnuBg":"#b97f55","mnuTxt":"#e8e8e8","mnuItemHover":"rgba(0,0,0,.04)","mnuGrade1":"rgba(255,0,0,0)","mnuGrade2":"#9e6943","mnuGlow":"rgba(0,0,0,.175)","modalGlow":"rgba(0,0,0,.5)","overlay":"rgba(0,0,0,0.6)","olEffect":"blur(2px)","olTxt":"#e8e8e8"},"_id":"vT9ETZpivwi9hvmw"}
5 |
--------------------------------------------------------------------------------
/app/main-functions/deprecated/import-dumo-method-2.js:
--------------------------------------------------------------------------------
1 | /* A bit faster than method 1. Too much garbage collection. Child process will be alive for too long.
2 | Line by line method is only needed for fast-csv */
3 |
4 | const fs = require('fs');
5 | const es = require('event-stream');
6 | const csv = require('fast-csv');
7 | const path = require('path');
8 | require('v8').setFlagsFromString('--harmony'); //To fix the regex Lookbehind issue
9 |
10 | process.on('uncaughtException', function (error) {
11 | console.log(error);
12 | process.send(['import-failed', 'general']); //mainWindow.webContents.send('import-failed', 'general');
13 | });
14 |
15 | let args = process.argv.slice(2);
16 | let filePath = args[0];
17 |
18 | let lineNr = 0;
19 |
20 | importDump(filePath);
21 |
22 | function importDump(filePath) {
23 | let totalLines = 0;
24 |
25 | countFileLines(filePath).then(function (count) {
26 | console.log('Total line count : ', count);
27 | totalLines = count;
28 | startImport();
29 | }).catch(function (err) {
30 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read');
31 | console.log(err);
32 | });
33 |
34 | function startImport() {
35 |
36 | fs.truncate(path.join(process.cwd(), 'data', 'stage.csv'), 0, function (err) {
37 | let stage = fs.createWriteStream(path.join(process.cwd(), 'data', 'stage.csv'), {
38 | flags: 'a'
39 | }).on('error', function (err) {
40 | stage.close();
41 | console.log(err);
42 | process.send(['import-failed', 'temp']);// mainWindow.webContents.send('import-failed', 'temp');
43 | }).on('open', function () {
44 | let s = fs.createReadStream(filePath)
45 | .pipe(es.split())
46 | .pipe(es.mapSync(function (line) {
47 |
48 | s.pause();
49 |
50 | lineNr += 1;
51 |
52 | let formattedLine = line.replace(/(?!";)(? {
93 | let lineCount = 1;
94 | fs.createReadStream(filePath)
95 | .on("data", (buffer) => {
96 | let idx = -1;
97 | lineCount--;
98 | do {
99 | idx = buffer.indexOf(10, idx + 1);
100 | lineCount++;
101 | } while (idx !== -1);
102 | }).on("end", () => {
103 | resolve(lineCount);
104 | }).on("error", reject);
105 | });
106 | }
--------------------------------------------------------------------------------
/app/main-functions/search.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const papa = require('papaparse');
4 |
5 | process.on('uncaughtException', function (error) {
6 | console.log(error);
7 | process.send(['search-failed', 'general']); //mainWindow.webContents.send('search-failed', 'general');
8 | });
9 |
10 | let args = process.argv.slice(2);
11 | let query = args[0];
12 | let count = parseInt(args[1]);
13 | let smart = (args[2] === 'true');
14 | let inst = (args[3] === 'true');
15 | count = count > 0 ? count : 100;
16 | count = count > 10000 ? 10000 : count;
17 |
18 | let i = 1;
19 | let reg;
20 | let stream;
21 | let result = [];
22 |
23 | // Takes a database record and returns a search result object to be displayed
24 | function resultObjectTemplate(record) {
25 | let size = formatBytes(record['SIZE(BYTES)'], 1);
26 | let row = '' + record['#ADDED'] +
27 | ' ' + record['HASH(B64)'] +
28 | ' ' + record['NAME'] +
29 | ' ' + size + ' ';
30 | return {
31 | added: record['#ADDED'],
32 | name: record['NAME'],
33 | size: size,
34 | markup: row
35 | };
36 | }
37 |
38 | // Returns true of false for DB record based on regular search terms
39 | function regularCondition(record) {
40 | return record['NAME'] && record['NAME'].toUpperCase().indexOf(query.toUpperCase()) > -1;
41 | }
42 |
43 | // Returns true of false for DB record based on smart search terms
44 | function smartCondition(record) {
45 | return reg.test(record['NAME']);
46 | }
47 |
48 | // Processes and searches each chunk of DB as it is read into memory
49 | function procData(results, parser) {
50 | var condition = smart ? smartCondition : regularCondition;
51 |
52 | for (let c = 0; c < results.data.length; c++) {
53 | let record = results.data[c];
54 |
55 | if (condition(record)) {
56 | if (i > count) {
57 | parser.abort();
58 | stream.close();
59 | break;
60 | } else {
61 | result.push(resultObjectTemplate(record));
62 | i++;
63 | }
64 | }
65 | }
66 |
67 | if (inst && result.length > 0) {
68 | process.send(['search-update', result]); //mainWindow.webContents.send('search-update', result);
69 | result = [];
70 | }
71 | }
72 |
73 |
74 | let finSearch = function () {
75 | if (inst) {
76 | process.send(['search-success-inst', {
77 | resCount: --i
78 | }]); //mainWindow.webContents.send('search-success-inst');
79 | } else {
80 | process.send(['search-success', {
81 | resCount: --i,
82 | results: result
83 | }]); //mainWindow.webContents.send('search-success');
84 | }
85 |
86 | process.exit(0);
87 | };
88 |
89 |
90 | function search() {
91 |
92 | stream = fs.createReadStream(path.join(process.cwd(), 'data', 'processed.csv'))
93 | .once('open', function () {
94 | papa.parse(stream, {
95 | delimiter: ';',
96 | escapeChar: '\\',
97 | header: true,
98 | chunk: procData,
99 | complete: finSearch,
100 | error: function (error) {
101 | process.send(['search-failed', 'process']); //mainWindow.webContents.send('search-failed', 'process');
102 | console.log(error);
103 | }
104 | });
105 | })
106 | .on('error', function (err) {
107 | process.send(['search-failed', 'read']); //mainWindow.webContents.send('search-failed', 'read');
108 | console.log(err);
109 | });
110 |
111 | }
112 |
113 |
114 | function startSearch(text) {
115 | reg = new RegExp(regexify(text), 'i');
116 | search();
117 | }
118 |
119 | function escapeRegExp(text) {
120 | return text.replace(/[-[\]{}()*+?.,\\/^$|#\s]/g, '\\$&');
121 | }
122 |
123 | function regexify(text) {
124 | text = text.trim().replace(/(\s+)/g, ' ');
125 | let words = text.split(' ');
126 | let final = '';
127 | words.forEach(function (item) {
128 | final += '(?=.*' + escapeRegExp(item) + ')';
129 | });
130 | return final;
131 | }
132 |
133 | function formatBytes(bytes, decimals) {
134 | if (bytes === 0) return '0 Bytes';
135 | let k = 1024,
136 | dm = decimals || 2,
137 | sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
138 | i = Math.floor(Math.log(bytes) / Math.log(k));
139 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
140 | }
141 |
142 | startSearch(query);
--------------------------------------------------------------------------------
/app/main-functions/deprecated/import-dump-method-1.js:
--------------------------------------------------------------------------------
1 | /* Seems to be extremely slow. Too much garbage collection. Line by line method is only needed for fast-csv */
2 |
3 | const fs = require('fs-extra');
4 | const path = require('path');
5 | const csv = require('fast-csv');
6 | const readline = require('readline');
7 | require('v8').setFlagsFromString('--harmony'); //To fix the regex Lookbehind issue
8 |
9 | process.on('uncaughtException', function (error) {
10 | console.log(error);
11 | process.send(['import-failed', 'general']); //mainWindow.webContents.send('import-failed', 'general');
12 | });
13 |
14 | let args = process.argv.slice(2);
15 | let filePath = args[0];
16 |
17 | console.log(filePath);
18 |
19 | importDump(filePath);
20 |
21 | function importDump(filePath) {
22 | let totalLines = 0;
23 |
24 | countFileLines(filePath).then(function (count) {
25 | console.log(count);
26 | totalLines = count;
27 | startImport();
28 | }).catch(function (err) {
29 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read');
30 | console.log(err);
31 | });
32 |
33 | function startImport() {
34 | try {
35 | try {
36 | fs.truncateSync(path.join(process.cwd(), 'data', 'stage.csv'), 0);
37 | } catch (error) {
38 | console.log('Staging file not found');
39 | }
40 |
41 | let stage = fs.createWriteStream(path.join(process.cwd(), 'data', 'stage.csv'), {
42 | flags: 'a'
43 | }).on('error', function (err) {
44 | stage.close();
45 | console.log(err);
46 | process.send(['import-failed', 'temp']);// mainWindow.webContents.send('import-failed', 'temp');
47 | }).on('open', function () {
48 | // console.log('OPENED');
49 | let curLine = 0;
50 |
51 | let lineReader = readline.createInterface({
52 | input: fs.createReadStream(filePath)
53 | });
54 |
55 | lineReader.on('line', function (line) {
56 | lineReader.pause();
57 | // console.log('READ');
58 | let formattedLine = line.replace(/(?!";)(? {
107 | let lineCount = 1;
108 | fs.createReadStream(filePath)
109 | .on("data", (buffer) => {
110 | let idx = -1;
111 | lineCount--; // Because the loop will run once for idx=-1
112 | do {
113 | idx = buffer.indexOf(10, idx + 1);
114 | lineCount++;
115 | } while (idx !== -1);
116 | }).on("end", () => {
117 | resolve(lineCount);
118 | }).on("error", reject);
119 | });
120 | }
--------------------------------------------------------------------------------
/app/img/update_trcks.svg:
--------------------------------------------------------------------------------
1 |
2 | download
3 | cloud,arrow,save,clone,sync,get,retrieve,download,form
4 | cc-by
5 | n8fyph
6 |
--------------------------------------------------------------------------------
/app/js/clusterize.min.js:
--------------------------------------------------------------------------------
1 | /* Clusterize.js - v0.18.1 - 2018-01-02
2 | http://NeXTs.github.com/Clusterize.js/
3 | Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
4 |
5 | ;(function(q,n){"undefined"!=typeof module?module.exports=n():"function"==typeof define&&"object"==typeof define.amd?define(n):this[q]=n()})("Clusterize",function(){function q(b,a,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function n(b,a,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)}function r(b){return"[object Array]"===Object.prototype.toString.call(b)}function m(b,a){return window.getComputedStyle?window.getComputedStyle(a)[b]:
6 | a.currentStyle[b]}var l=function(){for(var b=3,a=document.createElement("b"),c=a.all||[];a.innerHTML="\x3c!--[if gt IE "+ ++b+"]>=l&&!c.tag&&(c.tag=b[0].match(/<([^>\s/]*)/)[1].toLowerCase()),1>=this.content_elem.children.length&&(a.data=this.html(b[0]+b[0]+b[0])),c.tag||(c.tag=this.content_elem.children[0].tagName.toLowerCase()),
11 | this.getRowsHeight(b))},getRowsHeight:function(b){var a=this.options,c=a.item_height;a.cluster_height=0;if(b.length&&(b=this.content_elem.children,b.length)){var d=b[Math.floor(b.length/2)];a.item_height=d.offsetHeight;"tr"==a.tag&&"collapse"!=m("borderCollapse",this.content_elem)&&(a.item_height+=parseInt(m("borderSpacing",this.content_elem),10)||0);"tr"!=a.tag&&(b=parseInt(m("marginTop",d),10)||0,d=parseInt(m("marginBottom",d),10)||0,a.item_height+=Math.max(b,d));a.block_height=a.item_height*a.rows_in_block;
12 | a.rows_in_cluster=a.blocks_in_cluster*a.rows_in_block;a.cluster_height=a.blocks_in_cluster*a.block_height;return c!=a.item_height}},getClusterNum:function(){this.options.scroll_top=this.scroll_elem.scrollTop;return Math.floor(this.options.scroll_top/(this.options.cluster_height-this.options.block_height))||0},generateEmptyRow:function(){var b=this.options;if(!b.tag||!b.show_no_data_row)return[];var a=document.createElement(b.tag),c=document.createTextNode(b.no_data_text);a.className=b.no_data_class;
13 | if("tr"==b.tag){var d=document.createElement("td");d.colSpan=100;d.appendChild(c)}a.appendChild(d||c);return[a.outerHTML]},generate:function(b,a){var c=this.options,d=b.length;if(de&&g++;f=l&&"tr"==this.options.tag){var c=document.createElement("div");for(c.innerHTML="";b=a.lastChild;)a.removeChild(b);for(c=this.getChildNodes(c.firstChild.firstChild);c.length;)a.appendChild(c.shift())}else a.innerHTML=b},getChildNodes:function(b){b=b.children;for(var a=[],c=0,d=b.length;c
2 |
3 |
4 |
5 | OfflineBay by m4heshd
6 |
7 |
8 | Stable version: [**`2.0.0`**](https://github.com/m4heshd/offlinebay/releases/tag/2.0.0)
9 |
10 | OfflineBay is a free and open-source tool created to hold and parse copies of torrent archive dumps. So it allows you to search and download torrents offline. OfflineBay can parse CSV files with many torrenting related added features. The parsing is programmed to match the structure of dump files created by The Pirate Bay. But any group or organization can follow that same structure to create dump files to be used with OfflineBay.
11 |
12 | **[PLEASE FOLLOW THIS VIDEO TUTORIAL BEFORE YOU START USING OFFLINEBAY](https://www.youtube.com/watch?v=nNjW_OUv-hE)**
13 |
14 |
15 |
16 | ### DISCLAIMER:
17 | > **OfflineBay is Free and Open-source software licensed under [MIT License](LICENSE). This software is created to parse CSV dump files created by a third party and any of the data other than this software is not provided by m4heshd. Use the data at your own risk. m4heshd will not be responsible for any of the data acquired using dump files. It is strongly advised not to use OfflineBay for any copyright infringing activities. OfflineBay will not hide or protect you from authorities if you engage in such activities.**
18 |
19 |
20 | # Downloads
21 |
22 | OfflineBay is distributed as a portable application in favor of the majority. Download the corresponding package and extract the **ZIP** file. All of the packages should have an executable (double-clickable) along with some other files. File named `OfflineBay.exe` or `OfflineBay.app` or `OfflineBay` would be the executable file.
23 |
24 | **NOTE:** Linux users may need to declare the file as an executable before running - `chmod +x OfflineBay`
25 |
26 | **Why downloads are larger than the previous version?**
27 | Well, OfflineBay 2 is created using [Electron](https://electronjs.org) and NodeJS. So the disk footprint could be a [little concerning](https://github.com/electron/electron/issues/2003). There is no way around this issue. This is one small compromise for a lot of improvements.
28 |
29 | - [All releases (with source)](https://github.com/m4heshd/offlinebay/releases)
30 | - Windows (x86 & x64) - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-Windows.zip)
31 | - MacOS - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-MacOS.zip)
32 | - Linux (x64) - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-Linux-x64.zip)
33 | - Linux (x86) - [Download](https://github.com/m4heshd/offlinebay/releases/download/2.0.0/OfflineBay-2.0.0-Linux-x86.zip)
34 |
35 | **WARNING:** Since OfflineBay is now open-source you could stumble upon some modified versions of OfflineBay. If you get infected with malware by going off the proper channels, i won't take any blame for them.
36 |
37 | If you're looking for the older version of OfflineBay, Downloads are available [here](https://pirates-forum.org/Thread-Release-OfflineBay-1-0-2-Download-torrents-from-thePirateBay-offline).
38 |
39 | **How to remove OfflineBay?**
40 | I personally believe that none of the applications should leave any residue in the removal process. Unlike other portable apps OfflineBay will not leave anything behind once you hit delete on the application folder. It's valid for all platforms. Everything will be removed including Dump files, configurations and error logs.
41 |
42 | # Build OfflineBay yourself
43 |
44 | You will need [NodeJS](https://nodejs.org) and [NPM](https://www.npmjs.com/) (Usually packed with NodeJS) installed on your computer to build OfflineBay yourself.
45 |
46 | **NOTE:** When creating OfflineBay i almost managed to completely avoid native modules. But there's a single module named `bufferutil` that's required by another module. You may run into a few errors when building if you don't have platform build tools for `node-gyp`. You can simply ignore those errors since the module is an optional dependency. Also since OfflineBay practically has no native dependencies, it's possible to cross-build without any complications. But i cannot guarantee on the outcome.
47 |
48 | Clone this repository, `cd` to that directory and run `npm run clean` before everything. Then run any of the following commands to build OfflineBay.
49 |
50 | - Windows (x86 & x64) - `npm run dist-win`
51 | - MacOS - `npm run dist-mac`
52 | - Linux (x64) - `npm run dist-linux-64`
53 | - Linux (x86) - `npm run dist-linux-32`
54 |
55 | # Support OfflineBay project
56 |
57 | Involvement as a contributor by adding a few lines of code, fixing a bug, respond to issues, testing etc.. would be one of the most helpful methods you could support the project. If you're not a programmer but a creative person, you could definitely get involved in the GUI part of OfflineBay. [Follow this link to learn about theming for OfflineBay](https://github.com/m4heshd/offlinebay-themes).
58 | If you have some spare coins laying around, you could throw some this way to buy me a coffee..
59 |
60 | - **BTC:** 1KpjcftYs8CnG7jVgmzSoKAVT7QGqisYBC
61 | - **ETH:** 0x1EE4130876B4f0aD2303a98787fd3746DD0C84F4
62 | - **BCH:** 1LsY4Ppvrfx21pzs7YaAC3ciLbdwUYj92q
63 |
64 | [](https://www.paypal.me/mpwk?locale.x=en_US)
65 |
66 | Or you can buy me a "ko-fi" by clicking this button
67 |
68 | [](https://ko-fi.com/m4heshd)
69 |
70 | # Got an Issue?
71 |
72 | [Follow this link](https://github.com/m4heshd/offlinebay/issues) to submit your issues. Remember to be descriptive when submitting issues. Also remember that issues area is not meant to ask for help. If you need help or you have any other questions regarding OfflineBay you can simply fire up a conversation on the [Suprbay thread](https://pirates-forum.org/Thread-Release-OfflineBay-v2-Open-source-and-No-more-Java-dependency).
73 | # Known issues
74 |
75 | **Linux:**
76 |
77 | - Splash window may have a [white background instead of transparency](https://github.com/electron/electron/issues/2170) [`unfixable/SW-HW dependent`]
78 | - DHT scraping may not work sometimes [`unable to trace the issue`]
79 |
80 | # Themes you say?
81 |
82 | You can create themes for OfflineBay just using CSS. [This repository](https://github.com/m4heshd/offlinebay-themes) is dedicated to OfflineBay themes. Visit there to learn more about creating themes.
83 |
84 | If you're a regular user, you can also follow the [same repository](https://github.com/m4heshd/offlinebay-themes) to download themes.
85 |
86 | # Changelog
87 |
88 | Complete Changelog is available [here](changelog.txt).
89 |
90 | # License
91 | OfflineBay is licensed under [MIT License](LICENSE). So you are allowed to use freely and modify the application. I will not be responsible for any outcome. Proceed with any action at your own risk.
92 |
--------------------------------------------------------------------------------
/app/js/bootstrap-notify.min.js:
--------------------------------------------------------------------------------
1 | /* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */
2 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append(' ')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}});
--------------------------------------------------------------------------------
/app/css/comp-override.css:
--------------------------------------------------------------------------------
1 | .radio,
2 | .checkbox {
3 | position: relative;
4 | display: block;
5 | }
6 | .radio label,
7 | .checkbox label {
8 | min-height: 18px;
9 | padding-left: 20px;
10 | margin-bottom: 0;
11 | font-weight: normal;
12 | cursor: pointer;
13 | }
14 | .radio input[type="radio"],
15 | .radio-inline input[type="radio"],
16 | .checkbox input[type="checkbox"],
17 | .checkbox-inline input[type="checkbox"] {
18 | position: absolute;
19 | margin-left: -20px;
20 | margin-top: 4px \9;
21 | }
22 | .radio-inline,
23 | .checkbox-inline {
24 | position: relative;
25 | display: inline-block;
26 | padding-left: 20px;
27 | margin-bottom: 0;
28 | vertical-align: middle;
29 | font-weight: normal;
30 | cursor: pointer;
31 | }
32 | .radio-inline + .radio-inline,
33 | .checkbox-inline + .checkbox-inline {
34 | margin-top: 0;
35 | margin-left: 10px;
36 | }
37 | input[type="radio"][disabled],
38 | input[type="checkbox"][disabled],
39 | input[type="radio"].disabled,
40 | input[type="checkbox"].disabled,
41 | fieldset[disabled] input[type="radio"],
42 | fieldset[disabled] input[type="checkbox"] {
43 | cursor: not-allowed;
44 | }
45 | .radio-inline.disabled,
46 | .checkbox-inline.disabled,
47 | fieldset[disabled] .radio-inline,
48 | fieldset[disabled] .checkbox-inline {
49 | cursor: not-allowed;
50 | }
51 | .radio.disabled label,
52 | .checkbox.disabled label,
53 | fieldset[disabled] .radio label,
54 | fieldset[disabled] .checkbox label {
55 | cursor: not-allowed;
56 | }
57 |
58 | .checkbox label,
59 | .radio label {
60 | display: block;
61 | padding-left: 30px;
62 | position: relative;
63 | }
64 | .checkbox,
65 | .radio,
66 | .checkbox-inline,
67 | .radio-inline {
68 | padding-top: 0;
69 | }
70 | .checkbox input[type=checkbox],
71 | .radio input[type=checkbox],
72 | .checkbox-inline input[type=checkbox],
73 | .radio-inline input[type=checkbox],
74 | .checkbox input[type=radio],
75 | .radio input[type=radio],
76 | .checkbox-inline input[type=radio],
77 | .radio-inline input[type=radio] {
78 | top: 0;
79 | left: 0;
80 | margin-left: 0;
81 | z-index: 1;
82 | cursor: pointer;
83 | opacity: 0;
84 | filter: alpha(opacity=0);
85 | margin-top: 0;
86 | transform: translate(0, -1.7px);
87 | }
88 | .checkbox.disabled,
89 | .radio.disabled,
90 | .checkbox-inline.disabled,
91 | .radio-inline.disabled {
92 | opacity: 0.6;
93 | filter: alpha(opacity=60);
94 | }
95 | .checkbox .input-helper:before,
96 | .radio .input-helper:before,
97 | .checkbox-inline .input-helper:before,
98 | .radio-inline .input-helper:before,
99 | .checkbox .input-helper:after,
100 | .radio .input-helper:after,
101 | .checkbox-inline .input-helper:after,
102 | .radio-inline .input-helper:after {
103 | position: absolute;
104 | content: "";
105 | transition: all;
106 | transition-duration: 150ms;
107 | }
108 | .checkbox .input-helper:before,
109 | .radio .input-helper:before,
110 | .checkbox-inline .input-helper:before,
111 | .radio-inline .input-helper:before {
112 | left: 0;
113 | border-width: 2px;
114 | border-style: solid;
115 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2);
116 | }
117 | .checkbox input,
118 | .checkbox-inline input {
119 | width: 17px;
120 | height: 17px;
121 | }
122 | .checkbox input:checked + .input-helper:after,
123 | .checkbox-inline input:checked + .input-helper:after {
124 | opacity: 1;
125 | filter: alpha(opacity=100);
126 | }
127 | .checkbox .input-helper:before,
128 | .checkbox-inline .input-helper:before {
129 | top: 1px;
130 | width: 17px;
131 | height: 17px;
132 | border-radius: 2px;
133 | transform: translate(0, -1.7px);
134 | }
135 | .checkbox .input-helper:after,
136 | .checkbox-inline .input-helper:after {
137 | opacity: 0;
138 | filter: alpha(opacity=0);
139 | content: '\f26b';
140 | font-family: 'Material-Design-Iconic-Font';
141 | position: absolute;
142 | font-size: 12px;
143 | left: 3px;
144 | top: 1px;
145 | font-weight: bold;
146 | color: var(--bodyBg);
147 | transform: translate(0, -1.7px);
148 | }
149 | .checkbox .input-helper:before,
150 | .checkbox-inline .input-helper:before {
151 | border-color: var(--chkUnchecked);
152 | }
153 | .checkbox input:checked + .input-helper:before,
154 | .checkbox-inline input:checked + .input-helper:before {
155 | background-color: var(--chkChecked);
156 | border-color: var(--chkChecked);
157 | }
158 | .radio input,
159 | .radio-inline input {
160 | width: 19px;
161 | height: 19px;
162 | }
163 | .radio input:checked + .input-helper:after,
164 | .radio-inline input:checked + .input-helper:after {
165 | transform: scale(1);
166 | background: var(--chkChecked);
167 | }
168 | .radio .input-helper:before,
169 | .radio-inline .input-helper:before {
170 | top: -1px;
171 | width: 19px;
172 | height: 19px;
173 | border-radius: 50%;
174 | border-color: var(--chkUnchecked);
175 | }
176 | .radio .input-helper:after,
177 | .radio-inline .input-helper:after {
178 | background: var(--chkUnchecked);
179 | width: 9px;
180 | height: 9px;
181 | border-radius: 50%;
182 | top: 4px;
183 | left: 5px;
184 | transform: scale(0);
185 | }
186 | .radio input:checked + .input-helper:before,
187 | .radio-inline input:checked + .input-helper:before {
188 | border-color: var(--chkChecked);
189 | }
190 | .checkbox-inline,
191 | .radio-inline {
192 | padding-left: 30px;
193 | }
194 | /* Dropdown */
195 | .dropdown-menu {
196 | position: absolute;
197 | left: 0;
198 | z-index: 9;
199 | display: none;
200 | min-width: 180px;
201 | list-style: none;
202 | font-size: 13px;
203 | text-align: left;
204 | background-color: var(--mnuBg);
205 | box-shadow: 0 6px 12px var(--mnuGlow);
206 | background-clip: padding-box;
207 | padding: 10px 0;
208 | border-radius: 3px;
209 | top: -1px;
210 | margin: 0;
211 | border: 0;
212 | }
213 |
214 | .dropdown-menu > li > a {
215 | display: block;
216 | font-weight: 400;
217 | color: var(--mnuTxt);
218 | padding: 10px 20px;
219 | text-decoration: none;
220 | clear: both;
221 | transition: background-color;
222 | transition-duration: 300ms;
223 | }
224 | .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover {
225 | text-decoration: none;
226 | color: var(--mnuTxt);
227 | background-color: var(--mnuItemHover);
228 | }
229 | .dropup .dropdown-menu {
230 | bottom: 0;
231 | margin-bottom: 0;
232 | }
233 | /* Tables */
234 | .table > thead > tr > td.active,
235 | .table > tbody > tr > td.active,
236 | .table > tfoot > tr > td.active,
237 | .table > thead > tr > th.active,
238 | .table > tbody > tr > th.active,
239 | .table > tfoot > tr > th.active,
240 | .table > thead > tr.active > td,
241 | .table > tbody > tr.active > td,
242 | .table > tfoot > tr.active > td,
243 | .table > thead > tr.active > th,
244 | .table > tbody > tr.active > th,
245 | .table > tfoot > tr.active > th {
246 | background-color: var(--tblActiveRow);
247 | }
248 | .table-hover > tbody > tr > td.active:hover,
249 | .table-hover > tbody > tr > th.active:hover,
250 | .table-hover > tbody > tr.active:hover > td,
251 | .table-hover > tbody > tr:hover > .active,
252 | .table-hover > tbody > tr.active:hover > th {
253 | transition: all;
254 | transition-duration: .3s;
255 | background-color: var(--tblActiveRowHover);
256 | }
257 | .table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td {
258 | padding: 15px;
259 | line-height: 1.42857143;
260 | vertical-align: top;
261 | border-top: 1px solid var(--tblCellBorder);
262 | }
263 | .table > thead > tr > th {
264 | border-bottom-width: 1px;
265 | border-bottom-color: var(--tblHeadBottomBorder);
266 | color: var(--bodyTxt);
267 | font-weight: 700;
268 | }
269 | .table--vmiddle th,
270 | .table--vmiddle td {
271 | vertical-align: middle;
272 | }
273 | @media (min-width: 768px) {
274 | .table-striped:not(.table-bordered) > thead > tr > th,
275 | .table-striped:not(.table-bordered) > tbody > tr > td {
276 | vertical-align: middle;
277 | }
278 | }
--------------------------------------------------------------------------------
/app/main-functions/import-dump.js:
--------------------------------------------------------------------------------
1 | /* Tested to be working with the TPB dump at best. Extremely fast, zero Memory leaks and no garbage collection needed.
2 | * Process exits immediately. Put together to work with papaparse */
3 |
4 | const fs = require('fs');
5 | const path = require('path');
6 | const zlib = require('zlib');
7 | require('v8').setFlagsFromString('--harmony'); //To fix the regex Lookbehind issue
8 |
9 | // Keep the application from crashing on unexpected errors
10 | process.on('uncaughtException', function (error) {
11 | console.log(error);
12 | process.send(['import-failed', 'general']); //mainWindow.webContents.send('import-failed', 'general');
13 | });
14 |
15 | let args = process.argv.slice(2);
16 | let isUpd = (args[0] === 'true');
17 | let filePath = args[1];
18 | let timestamp = args[2];
19 | let keepDL = (args[3] === 'true');
20 | let stagePath = path.join(process.cwd(), 'data', 'stage.csv');
21 | let extract = path.join(process.cwd(), 'data', 'downloads', 'torrent_dump_full.csv');
22 | let totalLines = 0;
23 |
24 | // Determine if the file is a compressed dump or an extracted CSV and proceed to the corresponding function
25 | let ext = path.extname(filePath).toLowerCase();
26 | if (ext === '.gz') {
27 | decompressDump();
28 | } else if (ext === '.csv') {
29 | extract = filePath;
30 | startCSV();
31 | } else {
32 | process.send(['import-failed', 'invalid']);// mainWindow.webContents.send('import-failed', 'invalid');
33 | }
34 |
35 | // Validate and start the importing process
36 | function startCSV(){
37 | validate().then(function () {
38 | countFileLines(extract).then(function (count) {
39 | totalLines = count;
40 | startImport();
41 | }).catch(function (err) {
42 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read');
43 | console.log(err);
44 | });
45 | }).catch(function (err) {
46 | switch (err) {
47 | case 0:
48 | process.send(['import-failed', 'invalid']);// mainWindow.webContents.send('import-failed', 'invalid');
49 | break;
50 | default:
51 | process.send(['import-failed', 'read']);// mainWindow.webContents.send('import-failed', 'read');
52 | break;
53 | }
54 | });
55 | }
56 |
57 | // Validate the header of the CSV file to identify if it's a TPB dump
58 | function validate() {
59 | return new Promise((resolve, reject) => {
60 | process.send(['import-validate']);// mainWindow.webContents.send('import-validate');
61 | let reader = fs.createReadStream(extract)
62 | .on("data", function (buffer) {
63 | let header = buffer.toString().split('\n')[0];
64 | reader.close();
65 | if (header === '#ADDED;HASH(B64);NAME;SIZE(BYTES)') {
66 | resolve();
67 | } else {
68 | reject(0);
69 | }
70 | })
71 | .on("error", function (err) {
72 | console.log(err);
73 | reject(1);
74 | })
75 | });
76 | }
77 |
78 | // Truncate the staging file if exist or create, Process the data and import.
79 | function startImport() {
80 | let lineCount = 1;
81 | let stage = fs.createWriteStream(stagePath).on('error', function (err) {
82 | stage.close();
83 | console.log(err);
84 | process.send(['import-failed', 'temp']);// mainWindow.webContents.send('import-failed', 'temp');
85 | }).on('open', function () {
86 | fs.createReadStream(extract)
87 | .on("data", function (buffer) {
88 | let idx = -1;
89 | lineCount--;
90 | let formattedLine = buffer.toString().replace(/(?!";)(? {
222 | let lineCount = 1;
223 | fs.createReadStream(filePath)
224 | .on("data", (buffer) => {
225 | let idx = -1;
226 | lineCount--;
227 | do {
228 | idx = buffer.indexOf(10, idx + 1);
229 | lineCount++;
230 | } while (idx !== -1);
231 | }).on("end", () => {
232 | resolve(lineCount);
233 | }).on("error", reject);
234 | });
235 | }
--------------------------------------------------------------------------------
/app/css/WndMain.css:
--------------------------------------------------------------------------------
1 | /* Global
2 | --------------*/
3 | * {
4 | -webkit-user-drag: none;
5 | }
6 | body {
7 | font-family: 'Nunito', sans-serif;
8 | font-size: 13px;
9 | line-height: 1.42857143;
10 | color: var(--bodyTxt);
11 | background-color: var(--bodyBg);
12 | -webkit-user-select: none;
13 | }
14 | .body-container {
15 | display: grid;
16 | grid-template-rows: 30px 30px minmax(0px, auto) 40px;
17 | height: 100vh;
18 | }
19 | button:focus, input:focus {
20 | outline: none; !important;
21 | }
22 | /* Table */
23 | table {
24 | cursor: default;
25 | /*transform: rotateX(0deg);*/
26 | }
27 | thead > tr > th:hover {
28 | background: var(--tblHeadHover);
29 | }
30 | thead {
31 | background: var(--compClr);
32 | }
33 | th[data-role=columnheader] {
34 | cursor: pointer;
35 | }
36 |
37 | th[data-role=columnheader]:after {
38 | font: normal normal normal 14px/1 'Material-Design-Iconic-Font';
39 | font-size: 18px;
40 | content: '\f1cd';
41 | float: right;
42 | visibility: hidden;
43 | opacity: 0;
44 | }
45 | th[data-sort=desc]:after {
46 | content: '\f1ce';
47 | }
48 | th[data-sort=asc]:after {
49 | content: '\f1cd';
50 | }
51 | th[data-sort]:after {
52 | visibility: visible;
53 | opacity: 0.4;
54 | }
55 | th[data-role=columnheader]:hover:after {
56 | visibility: visible;
57 | opacity: 1;
58 | }
59 | #tblHead{
60 | margin-bottom: 0;
61 | }
62 | #tblMain > thead > tr > th {
63 | border-bottom: none;
64 | }
65 | #tblMain thead{
66 | visibility: hidden;
67 | }
68 | .sticky-top{
69 | z-index: 1;
70 | }
71 |
72 | span, strong {
73 | cursor: default;
74 | }
75 | /* Notifications */
76 | .alert-or {
77 | box-shadow: 0 0 8px 3px rgba(0,0,0,0.16);
78 | }
79 | .close-or {
80 | transform: translate(0, -3px);
81 | }
82 | /* Grid Layout
83 | --------------------*/
84 | .item-v-center{
85 | display: flex;
86 | align-items: center;
87 | flex: 1;
88 | }
89 | /* Title bar */
90 | .grid-title {
91 | display: grid;
92 | grid-template-columns: 80px auto repeat(3, 48px);
93 | grid-template-rows: 30px;
94 | grid-template-areas:
95 | "appTitle glue-title btnMin btnMax btnExit";
96 | background-color: var(--compClr);
97 | -webkit-app-region: drag;
98 | }
99 | .app-icon {
100 | margin-left: 10px;
101 | }
102 | .app-title {
103 | margin-left: 10px;
104 | }
105 | .btn-ctl {
106 | border: 0;
107 | height: 100%;
108 | width: 100%;
109 | background-color: var(--compClr);
110 | color: var(--bodyTxt);
111 | font-family: fantasy;
112 | -webkit-app-region: no-drag;
113 | }
114 | .btn-ctl i {
115 | font-size: 17px;
116 | /*font-weight: bold;*/
117 | }
118 | .btn-ctl:focus {
119 | border: 1px;
120 | outline-style: none;
121 | box-shadow: none !important;
122 | }
123 | /* Menu bar */
124 | .grid-menu {
125 | display: grid;
126 | grid-template-columns: 60px 70px 60px;
127 | padding-top: 3px;
128 | padding-left: 10px;
129 | background: linear-gradient(to top, var(--mnuGrade1), var(--mnuGrade2));
130 | /*-webkit-app-region: drag;*/
131 | }
132 | .mnu-itm {
133 | border: 0;
134 | height: 30px;
135 | width: 100%;
136 | border-radius: 3px;
137 | background-color: var(--mnuBtnBg);
138 | color: var(--bodyTxt);
139 | -webkit-app-region: no-drag;
140 | }
141 | .mnu-itm:focus, .mnu-itm:active {
142 | outline-style: none;
143 | box-shadow: none;
144 | }
145 | .mnu-itm:active {
146 | background: var(--bodyBg);
147 | }
148 | .mnu-itm[aria-expanded="true"] {
149 | background: linear-gradient(to bottom, var(--mnuGrade1), var(--mnuGrade2));
150 | }
151 | .mnu-ico {
152 | font-size: 18px;
153 | font-weight: bold;
154 | vertical-align: middle;
155 | padding-right: 10px;
156 | }
157 | .mnu-txt {
158 | vertical-align: middle;
159 | cursor: pointer;
160 | }
161 | /* Body */
162 | .grid-body {
163 | display: grid;
164 | grid-template-columns: auto 180px;
165 | grid-template-rows: 49px 47px 65px 55px 55px 55px 144px auto minmax(50px, 163px);
166 | grid-template-areas:
167 | "txtSearch btnSearch"
168 | "searchTools ."
169 | "tbl btnOpenMag"
170 | "tbl btnCopyMag"
171 | "tbl btnHash"
172 | "tbl btnGoogle"
173 | "tbl seedPanel"
174 | "tbl glue-1"
175 | "tbl TTLogo";
176 | grid-column-gap: 20px;
177 | /*height: 1fr;*/
178 | padding: 23px;
179 | padding-top: 5px;
180 | }
181 |
182 | .txtSearch {
183 | grid-area: txtSearch;
184 | }
185 | .btnSearch {
186 | grid-area: btnSearch;
187 | }
188 | .grid-search-tools {
189 | grid-area: searchTools;
190 | display: grid;
191 | grid-template-columns: 130px 95px 80px minmax(auto, 125px) 60px auto;
192 | padding-left: 15px;
193 | padding-right: 15px;
194 | padding-top: 15px;
195 | }
196 | .tbl-grid {
197 | grid-area: tbl;
198 | overflow-y: auto;
199 | margin-top: 20px;
200 | border-radius: 4px;
201 | background: var(--compClr);
202 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2);
203 | }
204 | .btnOpenMag {
205 | grid-area: btnOpenMag;
206 | margin-top: 20px;
207 | }
208 | .btnCopyMag {
209 | grid-area: btnCopyMag;
210 | margin-top: 10px;
211 | }
212 | .btnHash {
213 | grid-area: btnHash;
214 | margin-top: 10px;
215 | }
216 | .btnGoogle {
217 | grid-area: btnGoogle;
218 | margin-top: 10px;
219 | }
220 | .seedPanel {
221 | grid-area: seedPanel;
222 | margin-top: 30px;
223 | visibility: hidden;
224 | }
225 | .glue-1 {
226 | grid-area: glue-1;
227 | }
228 | .ttLogo {
229 | grid-area: TTLogo;
230 | }
231 |
232 | /* Status bar */
233 | .grid-stat-bar {
234 | display: grid;
235 | grid-template-columns: auto auto;
236 | border-top: 1px solid var(--compClr);
237 | margin: 0 12px;
238 | }
239 | #txtStat {
240 | margin: 10px 0 10px 10px;
241 | }
242 | #txtStatRight {
243 | visibility: hidden;
244 | width: 100%;
245 | text-align: right;
246 | margin: 10px 10px 10px 0;
247 | }
248 |
249 | /* About Modal */
250 | .abt-modal-content {
251 | display: grid;
252 | grid-template-rows: auto auto auto auto;
253 | padding: 30px;
254 | padding-top: 0;
255 | }
256 | .abt-title {
257 | margin-bottom: 15px;
258 | text-align: center;
259 | }
260 | .abt-title span {
261 | font-size: 25px;
262 | font-weight: bold;
263 | }
264 | .social-btns {
265 | display: flex;
266 | justify-content: center;
267 | margin: 20px 0;
268 | }
269 | .social-btns img {
270 | margin: 0 10px;
271 | cursor: pointer;
272 | }
273 | .social-btns img:hover {
274 | -webkit-transition: transform 0.3s ease;
275 | transform: scale(1.1);
276 | }
277 |
278 | /* Trackers Modal */
279 | .mdl-trackers {
280 | height: 500px;
281 | }
282 | .trackers-modal-content {
283 | display: grid;
284 | grid-template-columns: auto 120px;
285 | grid-template-rows: 55px 55px minmax(0px, auto);
286 | grid-template-areas:
287 | "tbl btnCopyTrck"
288 | "tbl btnCopyAllTrck"
289 | "tbl .";
290 | grid-column-gap: 20px;
291 | padding: 20px 10px 10px;
292 | }
293 | .trackers-tbl {
294 | grid-area: tbl;
295 | overflow-y: auto;
296 | border-radius: 4px;
297 | background: var(--compClr);
298 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2);
299 | }
300 | .btnCopyTrck {
301 | grid-area: btnCopyTrck;
302 | }
303 | .btnCopyAllTrck {
304 | grid-area: btnCopyAllTrck;
305 | }
306 | #tblTrackers td{
307 | padding: 10px;
308 | }
309 |
310 | /* Preferences Modal */
311 | .mdl-prefs {
312 | height: 500px;
313 | }
314 | .prefs-modal-content {
315 | display: grid;
316 | grid-template-columns: auto;
317 | grid-template-rows: auto 55px;
318 | grid-row-gap: 20px;
319 | padding: 20px 10px 10px;
320 | }
321 | .prefs-opts {
322 | overflow-y: auto;
323 | }
324 | .prefs-opts hr {
325 | border-top: 1px solid var(--compClr);
326 | }
327 | .prefs-section{
328 | margin: 30px 0;
329 | }
330 | .prefs-itm {
331 | margin-top: 20px;
332 | }
333 | .prefs-span-txt {
334 | display: grid;
335 | grid-template-columns: 90px auto;
336 | margin-right: 10px;
337 | }
338 | .prefs-upd-int {
339 | display: grid;
340 | grid-template-columns: 178px 60px 50px;
341 | grid-column-gap: 5px;
342 | }
343 | .prefs-btns {
344 | display: grid;
345 | grid-template-columns: auto 120px 120px;
346 | grid-column-gap: 10px;
347 | }
348 |
349 | /* Themes Modal */
350 | .mdl-themes {
351 | height: 500px;
352 | }
353 | .themes-modal-content {
354 | display: grid;
355 | grid-template-columns: auto;
356 | grid-template-rows: auto 55px;
357 | grid-row-gap: 20px;
358 | padding: 20px 10px 10px;
359 | }
360 | .themes-tiles {
361 | display: grid;
362 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
363 | grid-gap: 20px;
364 | padding: 20px 10px 10px;
365 | overflow-y: auto;
366 | }
367 | .theme-prev-bg {
368 | height: 130px;
369 | width: 200px;
370 | margin: 0 auto;
371 | border-radius: 7px;
372 | border: solid 1px var(--bodyTxt);
373 | text-align: center;
374 | justify-content: center;
375 | }
376 | .theme-prev-content {
377 | text-align: center;
378 | }
379 | .btn-theme-apply {
380 | margin: 15px 0;
381 | width: 120px !important;
382 | display: block !important;
383 | box-shadow: 0 0 14px 1px var(--btnFocusGlow);
384 | }
385 | .btn-theme-del {
386 | width: 30px !important;
387 | height: 30px !important;
388 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2) !important;
389 | }
390 | .btn-theme-del.thm-default-btn {
391 | visibility: hidden;
392 | }
393 | .btn-theme-del > i {
394 | padding: 0 !important;
395 | margin-top: -13px !important;
396 | font-size: 16px;
397 | }
398 | .themes-btns {
399 | display: grid;
400 | grid-template-columns: auto 120px 120px 120px;
401 | grid-column-gap: 10px;
402 | }
403 |
404 | /* Overlay */
405 | #overlay {
406 | display: grid;
407 | grid-template-rows: 33% 180px 70px auto;
408 | text-align: center;
409 | position: fixed;
410 | visibility: hidden;
411 | opacity: 0;
412 | width: 100%;
413 | height: 100%;
414 | top: 0;
415 | left: 0;
416 | right: 0;
417 | bottom: 0;
418 | background-color: var(--overlay);
419 | z-index: 1002;
420 | cursor: wait;
421 | transition: all .2s ease;
422 | }
423 |
424 | #olText{
425 | color: var(--olTxt);
426 | font-size: 40px;
427 | }
428 |
429 | /* Global Elements
430 | --------------------*/
431 | /*Button*/
432 | .btn-ui {
433 | border: 0;
434 | height: 45px;
435 | width: 100%;
436 | padding: 10px;
437 | border-radius: 4px;
438 | background-color: var(--compClr);
439 | font-size: 15px;
440 | color: var(--btnTxtClr);
441 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2);
442 | }
443 | .btn-ico {
444 | font-size: 20px;
445 | vertical-align: middle;
446 | padding-right: 5px;
447 | }
448 | .btn-themed:hover {
449 | color: var(--btnTxtHover);
450 | background-color: var(--btnBgHover);
451 | border-color: var(--btnActive);
452 | }
453 | .btn-themed:focus, .btn-themed.focus {
454 | box-shadow: 0 0 0 0.2rem var(--btnFocusGlow);
455 | }
456 | .btn-themed.disabled, .btn-themed:disabled {
457 | color: var(--btnTxtDisable);
458 | background-color: var(--btnBgDisable);
459 | border-color: var(--btnBorderDisable);
460 | }
461 | .btn-themed:not(:disabled):not(.disabled):active, .btn-themed:not(:disabled):not(.disabled).active,
462 | .show > .btn-themed.dropdown-toggle {
463 | color: var(--btnTxtHover);
464 | background-color: var(--btnActive);
465 | }
466 | .btn-themed:not(:disabled):not(.disabled):active:focus, .btn-themed:not(:disabled):not(.disabled).active:focus,
467 | .show > .btn-themed.dropdown-toggle:focus {
468 | box-shadow: 0 0 0 0.2rem var(--btnFocusGlow);
469 | }
470 |
471 | /*Text box*/
472 | .txtbox {
473 | border: 0;
474 | height: inherit;
475 | padding: 10px 10px 10px 10px;
476 | border-radius: 4px;
477 | width: 100%;
478 | background-color: var(--compClr);
479 | font-size: 15px;
480 | color: var(--bodyTxt);
481 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2);
482 | }
483 | .txtbox:focus {
484 | background: var(--txtFocusBg);
485 | color: var(--txtFocustxt);
486 | }
487 | .txtbox::-webkit-input-placeholder {
488 | color: var(--bodyTxt)
489 | }
490 | .txtbox:focus::-webkit-input-placeholder {
491 | color: var(--txtFocusPH)
492 | }
493 | .txtboxsm {
494 | height: 32px;
495 | }
496 | .txtinvalid, .txtinvalid:focus{
497 | color: red;
498 | }
499 | /*Modal*/
500 | .modal {
501 | text-align: center;
502 | }
503 | .modal:before {
504 | display: inline-block;
505 | vertical-align: middle;
506 | content: " ";
507 | height: 100%;
508 | }
509 | .modal-close {
510 | display: flex;
511 | justify-content: flex-end;
512 | }
513 | .mdl-ctl {
514 | width: 30px;
515 | height: 30px;
516 | border-radius: 50%;
517 | background: none;
518 | }
519 | .modal-dialog {
520 | display: inline-block;
521 | min-width: 600px;
522 | text-align: left;
523 | vertical-align: middle;
524 | }
525 | .modal-body {
526 | display: grid;
527 | padding: 10px;
528 | }
529 | .modal-window {
530 | height: 100%;
531 | grid-template-rows: 30px minmax(0px, auto);
532 | }
533 | .modal-content {
534 | background: var(--bodyBg);
535 | box-shadow: 0 3px 15px var(--modalGlow);
536 | }
537 | .modal-titlebar {
538 | display: grid;
539 | grid-template-columns: auto 30px;
540 | grid-template-rows: 30px;
541 | border-bottom: var(--compClr) 1px solid;
542 | min-height: 35px;
543 | cursor: -webkit-grab;
544 | -webkit-app-region: no-drag;
545 | }
546 | .modal-titlebar:active, .modal-titlebar-txt span:active{
547 | cursor: -webkit-grabbing;
548 | }
549 | .modal-titlebar-txt {
550 | display: flex;
551 | align-items: center;
552 | }
553 | .mdl-ico {
554 | vertical-align: middle;
555 | font-size: 18px;
556 | padding-left: 5px;
557 | }
558 | .modal-titlebar-txt span{
559 | font-weight: bold;
560 | font-size: 15px;
561 | padding-left: 10px;
562 | cursor: -webkit-grab;
563 | }
564 | .ui-icon, .ui-widget-content .ui-icon {
565 | background-image: url(../img/ui-icons_444444_256x240.png);
566 | }
567 | .ui-icon-gripsmall-diagonal-se {
568 | background-position: -80px -224px;
569 | }
570 | .ui-resizable-se {
571 | width: 16px;
572 | height: 16px;
573 | }
574 | /* Scrollbars */
575 | *::-webkit-scrollbar {
576 | width: 10px !important;
577 | height: 10px !important;
578 | background: var(--scrollBg) !important;
579 | border: 1px solid var(--scrollBorder) !important;
580 | }
581 | *::-webkit-scrollbar-button {
582 | display: none !important;
583 | }
584 | *::-webkit-scrollbar-thumb,
585 | *::-webkit-scrollbar-track {
586 | border: 0 !important;
587 | box-shadow: none !important;
588 | }
589 | *::-webkit-scrollbar-thumb {
590 | min-height: 28px !important;
591 | background: var(--scrollThumb) !important;
592 | }
593 | *::-webkit-scrollbar-corner,
594 | *::-webkit-scrollbar-track {
595 | background: var(--scrollBg) !important;
596 | }
597 | /* Tooltip */
598 | .tooltiptext {
599 | visibility: hidden;
600 | position: absolute;
601 | width: 120px;
602 | background-color: var(--mnuBg);
603 | color: var(--mnuTxt);
604 | text-align: center;
605 | padding: 5px 0;
606 | border-radius: 6px;
607 | z-index: 1;
608 | opacity: 0;
609 | transition: opacity .6s;
610 | bottom: 165%;
611 | left: 50%;
612 | margin-left: -60px;
613 | }
614 | .tooltiptext::after {
615 | content: "";
616 | position: absolute;
617 | top: 100%;
618 | left: 50%;
619 | margin-left: -5px;
620 | border: 5px solid transparent;
621 | border-top-color: var(--mnuBg);
622 | }
623 | .show-tooltip:hover .tooltiptext {
624 | visibility: visible;
625 | opacity: 1;
626 | }
627 |
628 | /*------END GLOBAL------*/
629 |
630 | /* Search Input
631 | --------------------*/
632 | .top-search, .top-search__input,
633 | .top-search__reset:after,
634 | .top-search__reset:before,
635 | .btn-ui, .txtbox,
636 | .table-hover tbody tr:hover,
637 | thead > tr > th:hover,
638 | .btn-ctl,
639 | .mnu-itm{
640 | -webkit-transition: all;
641 | -o-transition: all;
642 | transition: all;
643 | -webkit-transition-duration: .3s;
644 | transition-duration: .3s
645 | }
646 |
647 | .top-search {
648 | position: relative;
649 | margin: 2px 0;
650 | overflow: visible;
651 | box-shadow: 0 2px 2px 0 var(--compShadow1), 0 0 0 1px var(--compShadow2);
652 | /*display: inline;*/
653 | display: inline-block;
654 | }
655 |
656 | .top-search__input {
657 | border: 0;
658 | height: 45px;
659 | padding: 10px 20px 10px 50px;
660 | border-radius: 4px;
661 | width: 100%;
662 | background-color: var(--compClr);
663 | font-size: 15px;
664 | color: var(--txtFocustxt)
665 | }
666 |
667 | .top-search__input::-webkit-input-placeholder {
668 | color: var(--bodyTxt)
669 | }
670 |
671 | .top-search--focused .top-search__input {
672 | background-color: var(--txtFocusBg)
673 | }
674 |
675 | .top-search--focused .top-search__input + .top-search__reset {
676 | color: var(--txtFocusBg)
677 | }
678 |
679 | .top-search--focused .top-search__input + .top-search__reset:before {
680 | -webkit-transform: scale(0) rotate(180deg);
681 | -ms-transform: scale(0) rotate(180deg);
682 | -o-transform: scale(0) rotate(180deg);
683 | transform: scale(0) rotate(180deg)
684 | }
685 |
686 | .top-search--focused .top-search__input + .top-search__reset:after {
687 | -webkit-transform: scale(1) rotate(0);
688 | -ms-transform: scale(1) rotate(0);
689 | -o-transform: scale(1) rotate(0);
690 | transform: scale(1) rotate(0)
691 | }
692 |
693 | .top-search--focused .top-search__input::-webkit-input-placeholder {
694 | color: var(--txtFocusPH)
695 | }
696 |
697 |
698 | .top-search__reset {
699 | left: 0;
700 | font-size: 20px;
701 | color: var(--bodyTxt);
702 | top: 0;
703 | position: absolute;
704 | cursor: pointer;
705 | width: 50px;
706 | height: 100%
707 | }
708 |
709 | .top-search__reset:hover {
710 | opacity: .75;
711 | filter: alpha(opacity=75)
712 | }
713 |
714 | .top-search__reset:after, .top-search__reset:before {
715 | position: absolute;
716 | left: 20px;
717 | top: 14px
718 | }
719 |
720 | .top-search__reset:after {
721 | content: '\f2ff';
722 | -webkit-transform: scale(0) rotate(-180deg);
723 | -ms-transform: scale(0) rotate(-180deg);
724 | -o-transform: scale(0) rotate(-180deg);
725 | transform: scale(0) rotate(-180deg);
726 | color: var(--txtFocustxt)
727 | }
728 |
729 | /* Seeds
730 | --------------------*/
731 | .seeds {
732 | margin-top: 20px;
733 | }
734 | .seeds i {
735 | display:inline-block;
736 | font-size: 30px;
737 | }
738 | .seeds span{
739 | display:inline-block;
740 | vertical-align:middle;
741 | font-size: 20px;
742 | font-weight: bold;
743 | padding-left: 10px;
744 | }
745 | .seeds img{
746 | display: inline-block;
747 | vertical-align: middle;
748 | margin-left: 5px;
749 | width: 25px;
750 | }
751 |
752 | /* TechTac Logo
753 | --------------------*/
754 | .img-helper {
755 | display: inline-block;
756 | height: 100%;
757 | vertical-align: middle;
758 | }
759 | .tt-img{
760 | height: 130px;
761 | max-height: 80%;
762 | text-align: center;
763 | }
764 | .tt-img img{
765 | /*width: 127px;*/
766 | vertical-align:middle;
767 | max-height: 100%;
768 | }
769 | .tt-img img:hover {
770 | -webkit-transition: transform 0.3s ease;
771 | transform: scale(1.04);
772 | cursor: pointer;
773 | }
774 |
775 | /* Support notification
776 | ------------------------*/
777 | .support-alert {
778 | min-width: 470px;
779 | max-width: 524px;
780 | }
781 | .support-body {
782 | display: grid;
783 | grid-template-columns: 90px auto;
784 | grid-column-gap: 7px;
785 | }
786 | .support-alert h2, .support-alert h5 {
787 | font-weight: 700;
788 | }
789 | .support-body span {
790 | font-size: 14px;
791 | display: block;
792 | }
793 | .support-body span a:hover {
794 | color: darkred !important;
795 | cursor: pointer;
796 | }
797 |
798 | /* Autocomplete
799 | ----------------*/
800 | .autocomplete-items {
801 | position: absolute;
802 | display: table;
803 | z-index: 8;
804 | top: 100%;
805 | left: 40px;
806 | right: 0;
807 | background-color: var(--txtFocusBg);
808 | border-radius: 4px;
809 | box-shadow: 0 6px 12px var(--mnuGlow);
810 | }
811 | .autocomplete-items div {
812 | cursor: pointer;
813 | color: var(--txtFocustxt);
814 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
815 | transition: all;
816 | transition-duration: 300ms;
817 | padding: 10px 50px 10px 10px;
818 | }
819 | .autocomplete-items div strong {
820 | font-weight: 800;
821 | }
822 | .autocomplete-items div:hover {
823 | background-color: rgba(0, 0, 0, 0.1);
824 | }
825 | .autocomplete-active {
826 | background-color: rgba(0, 0, 0, 0.1);
827 | }
--------------------------------------------------------------------------------
/app/WndMain.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OfflineBay by m4heshd
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
137 |
138 |
139 |
140 |
142 |
143 |
144 |
145 |
146 |
147 | Search
148 |
149 |
150 |
181 |
207 |
208 |
209 |
210 | Open Magnet
211 |
212 |
213 |
214 |
215 |
216 | Copy Magnet
217 |
218 |
219 |
220 |
221 |
222 | Copy Info Hash
223 |
224 |
225 |
226 |
227 |
228 | Google Torrent
229 |
230 |
231 |
232 |
233 | Seeds/Peers
234 |
235 |
236 |
237 |
5623
238 |
239 |
240 |
241 |
242 |
1200
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | Ready...
262 |
263 |
264 | Ready...
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 | OfflineBay by m4heshd
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 | DISCLAIMER:
298 | OfflineBay is Free and Open-source software licenced under MIT License.
299 | This software is created to parse CSV dump files created by a third party and any of the data
300 | other than this software is not provided by m4heshd. Use the data at your own risk. m4heshd will not
301 | be responsible for any of the data acquired using the dump file.
302 |
303 | Software version : N/A
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 | All Trackers
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
335 |
336 |
337 |
338 | Copy
339 |
340 |
341 |
342 |
343 |
344 | Copy All
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 | Preferences
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
General
373 |
374 |
375 |
376 |
377 | Close to system tray
378 |
379 |
380 |
381 |
382 |
383 |
384 | Log to file
385 |
386 |
387 |
388 |
389 |
390 |
Trackers
391 |
392 |
393 | Update URL :
394 |
395 |
396 |
399 |
400 |
401 |
402 |
403 |
404 |
Search
405 |
406 |
407 |
408 |
409 | Use Autocomplete
410 |
411 |
412 |
413 |
414 | Clear History
415 |
416 |
417 |
418 |
419 |
420 |
Seeds/Peers Discovery
421 |
422 |
423 |
424 |
425 | Use DHT (DHT + Trackers)
426 |
427 |
428 |
429 |
430 |
431 |
Dump Updates
432 |
433 |
434 | Update URL :
435 |
436 |
437 |
440 |
441 |
442 |
464 |
465 |
466 | Check for dump updates every
467 |
468 |
469 |
471 |
472 |
473 | Minutes
474 |
475 |
476 |
477 |
478 |
479 |
480 | Keep downloads after import
481 |
482 |
483 |
484 |
485 | Last Update :
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 | Reset Update
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 | Discard
504 |
505 |
506 |
507 |
508 |
509 | Save
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 | Themes
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 | Theme name
540 |
541 |
542 | Apply
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 | Download
556 |
557 |
558 |
559 |
560 |
561 | Import
562 |
563 |
564 |
565 |
567 |
568 | Ok
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 | Loading...
587 |
588 |
589 |
590 |
591 |
592 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
--------------------------------------------------------------------------------
/app/main.js:
--------------------------------------------------------------------------------
1 | /* QR9SkRpC1piyc/5wR87+vRl4oKxTFO28fjaeGeiafGCE1A== ;) */
2 |
3 | const cp = require('child_process');
4 | const electron = require('electron');
5 | const url = require('url');
6 | const path = require('path');
7 | const Datastore = require('nedb');
8 | const request = require('request');
9 | const fs = require('fs');
10 | const util = require('util');
11 | const moment = require('moment');
12 |
13 | const {app, BrowserWindow, ipcMain, dialog, Menu, Tray} = electron;
14 |
15 | let prefs = {
16 | maxed: false,
17 | position: [0, 0],
18 | size: [1200, 800],
19 | rs_count: 100,
20 | smart: true,
21 | inst: false,
22 | logToFile: true,
23 | confVer: "1.0"
24 | }; // Set initial Preferences
25 | let procImport;
26 | let procSearch;
27 | let procScrape;
28 | let procUpd;
29 | let scrapeOpts = [];
30 | let finalPrefs = false;
31 | let awaitingQuit = false;
32 | let awaitingScrape = false;
33 | let splashWindow;
34 | let mainWindow;
35 | let obTray;
36 | let appIcon = path.join(__dirname, 'img', 'icon.png');
37 | let trayIcon = path.join(__dirname, 'img', 'icon_32.png');
38 | let version = 'N/A';
39 |
40 | /* System handles
41 | ------------------*/
42 | let isMac = process.platform === 'darwin';
43 |
44 | // Allow only a single instance of OfflineBay
45 | let shouldQuit = app.makeSingleInstance(function() {
46 | if (mainWindow) {
47 | showWindow();
48 | }
49 | });
50 | if (shouldQuit) {
51 | app.quit();
52 | return;
53 | }
54 |
55 | app.setAppUserModelId(process.execPath); // To support Windows notifications
56 |
57 | // Log to file
58 | let logger = fs.createWriteStream(path.join(__dirname, 'data', 'logger.log'));
59 | logger.on('error', function (err) {
60 | console.log(err);
61 | logger = null;
62 | });
63 | console.log = function () {
64 | let data = util.format.apply(this, arguments) + '\n';
65 | if (prefs.logToFile) {
66 | if (logger) {
67 | logger.write(`[${moment().format('YYYY-MM-DD hh:mm:ss')}] : ${data}`);
68 | }
69 | process.stdout.write(data);
70 | } else {
71 | process.stdout.write(data);
72 | }
73 | };
74 |
75 | // Get current version from package.json
76 | try {
77 | const packageJSON = require(path.join(__dirname, '..', 'package.json'));
78 | if (packageJSON) {
79 | version = packageJSON.version;
80 | }
81 | } catch (error) {
82 | console.log(error);
83 | }
84 |
85 | // Set app & tray icons
86 | switch (process.platform) {
87 | case 'win32':
88 | appIcon = path.join(__dirname, 'img', 'icon.ico');
89 | trayIcon = appIcon;
90 | break;
91 | case 'darwin':
92 | appIcon = path.join(__dirname, 'img', 'icon.icns');
93 | trayIcon = path.join(__dirname, 'img', 'icon_16.png');
94 | app.dock.bounce('critical');
95 | break;
96 | }
97 |
98 | /* Process handles
99 | --------------------*/
100 | // Emitted if the window is waiting for child processes to exit
101 | process.on('cont-quit', function () {
102 | if (!procSearch && !procImport && !procScrape && !procUpd) {
103 | app.quit();
104 | }
105 | });
106 | // Emitted if the window is waiting for one scrape process to finish to start the next
107 | process.on('cont-scrape', function () {
108 | if (!procScrape) {
109 | initScrape(scrapeOpts[0], scrapeOpts[1]);
110 | }
111 | });
112 |
113 | process.on('uncaughtException', function (error) {
114 | console.log(error);
115 | if (mainWindow){
116 | popErr('An unknown error occurred');
117 | } else {
118 | app.quit();
119 | }
120 | });
121 |
122 | /* DB functions
123 | ----------------*/
124 | // Loads the configurations DB
125 | let config = new Datastore({
126 | filename: path.join(__dirname, 'data', 'config.db'),
127 | autoload: true
128 | });
129 |
130 | // Update the prefs object
131 | function updatePrefs() {
132 | prefs.maxed = mainWindow.isMaximized();
133 | if (!prefs.maxed) {
134 | prefs.position = mainWindow.getPosition();
135 | prefs.size = mainWindow.getSize();
136 | }
137 | }
138 |
139 | // Save the current prefs to config DB
140 | function saveSession() {
141 | return new Promise((resolve, reject) => {
142 | config.update({type: 'win-state'}, {
143 | $set: {
144 | maxed: prefs.maxed,
145 | position: prefs.position,
146 | size: prefs.size
147 | }
148 | }, {}, function (err, numReplaced) {
149 | if (err || numReplaced < 1) {
150 | console.log(err);
151 | }
152 | config.update({type: 'search'}, {
153 | $set: {
154 | rs_count: prefs.rs_count,
155 | smart: prefs.smart,
156 | inst: prefs.inst
157 | }
158 | }, {}, function (err, numReplaced) {
159 | if (err || numReplaced < 1) {
160 | console.log(err);
161 | }
162 | finalPrefs = true;
163 | resolve();
164 | });
165 | });
166 | });
167 | }
168 |
169 | // Update updLast on DB
170 | function saveUpdLast(updLast) {
171 | return new Promise((resolve, reject) => {
172 | config.update({type: 'dump'}, { $set: { updLast: updLast } }, function (err, numReplaced) {
173 | if (err || numReplaced < 1) {
174 | console.log(err);
175 | reject();
176 | } else {
177 | resolve();
178 | }
179 | })
180 | });
181 | }
182 |
183 | // Add new search history item to the DB
184 | function addHistoryItm(txt) {
185 | config.update({type: 'search'}, {$push: {history: txt}}, function (err, numReplaced) {
186 | if (err || numReplaced < 1) {
187 | console.log(err);
188 | }
189 | })
190 | }
191 |
192 | // Clear search history on DB
193 | function clearHistory() {
194 | return new Promise((resolve, reject) => {
195 | config.update({type: 'search'}, { $set: { history: [] } }, function (err, numReplaced) {
196 | if (err || numReplaced < 1) {
197 | console.log(err);
198 | reject();
199 | } else {
200 | resolve();
201 | }
202 | })
203 | });
204 | }
205 |
206 | // Update last support message shown timestamp on DB
207 | function updSupMsgDate() {
208 | config.update({type: 'gen'}, {$set: {supMsg: new Date().toISOString()}}, function (err, numReplaced) {
209 | if (err || numReplaced < 1) {
210 | console.log(err);
211 | console.log('An error occurred trying to update Support message timestamp');
212 | }
213 | })
214 | }
215 |
216 | // Load prefs from config DB and start OfflineBay
217 | function loadSession() {
218 | showSplash();
219 | config.findOne({type: 'win-state'}, function (err, dbPref) {
220 | if (!err && dbPref) {
221 | prefs.maxed = dbPref.maxed;
222 | prefs.position = dbPref.position;
223 | prefs.size = dbPref.size;
224 |
225 | config.findOne({type: 'gen'}, function (err, dbPref) {
226 | if (!err && dbPref) {
227 | prefs.logToFile = dbPref.logToFile;
228 | startOB();
229 | } else {
230 | setTimeout(popDbErr, 1500);
231 | }
232 | })
233 | } else {
234 | console.log(err);
235 | popDbErr();
236 | }
237 | })
238 | }
239 |
240 | // Get the endpoint URL to update trackers
241 | function getTrackerEP() {
242 | return new Promise((resolve, reject) => {
243 | config.findOne({type: 'trackers'}, function (err, trck) {
244 | if (!err && trck) {
245 | resolve(trck.url);
246 | } else {
247 | console.log(err);
248 | reject();
249 | }
250 | })
251 | });
252 | }
253 |
254 | // Update trackers list on DB
255 | function setTrackers(trcks) {
256 | return new Promise((resolve, reject) => {
257 | config.update({type: 'trackers'}, { $set: { trackers: trcks } }, function (err, numReplaced) {
258 | if (err || numReplaced < 1) {
259 | console.log(err);
260 | reject();
261 | } else {
262 | resolve();
263 | }
264 | })
265 | });
266 | }
267 |
268 | // Update preferences from renderer process
269 | function saveRndPrefs(rndPrefs) {
270 | return new Promise((resolve, reject) => {
271 | config.update({type: 'gen'}, {
272 | $set: {
273 | sysTray: rndPrefs.sysTray,
274 | useDHT: rndPrefs.useDHT,
275 | logToFile: prefs.logToFile
276 | }
277 | }, function (err, numReplaced) {
278 | if (err || numReplaced < 1) {
279 | console.log(err);
280 | reject();
281 | } else {
282 | config.update({type: 'trackers'}, {$set: {url: rndPrefs.trckURL}}, function (err, numReplaced) {
283 | if (err || numReplaced < 1) {
284 | console.log(err);
285 | reject();
286 | } else {
287 | config.update({type: 'dump'}, {
288 | $set: {
289 | updURL: rndPrefs.updURL,
290 | updType: rndPrefs.updType,
291 | updInt: rndPrefs.updInt,
292 | keepDL: rndPrefs.keepDL
293 | }
294 | }, function (err, numReplaced) {
295 | if (err || numReplaced < 1) {
296 | console.log(err);
297 | reject();
298 | } else {
299 | config.update({type: 'search'}, {
300 | $set: {
301 | useAC: rndPrefs.useAC
302 | }
303 | }, function (err, numReplaced) {
304 | if (err || numReplaced < 1) {
305 | console.log(err);
306 | reject();
307 | } else {
308 | resolve();
309 | }
310 | });
311 | }
312 | });
313 | }
314 | });
315 | }
316 | });
317 | });
318 | }
319 |
320 | function popDbErr() {
321 | dialog.showErrorBox('Critical error', 'DB error occurred. Please re-install OfflineBay');
322 | }
323 |
324 | /* Main App handles
325 | --------------------*/
326 | app.on('ready', loadSession);
327 |
328 | // Save the instance data to config DB before quitting
329 | app.on('will-quit', async function (event) {
330 | if (!finalPrefs) {
331 | event.preventDefault();
332 | await saveSession();
333 | app.quit();
334 | }
335 | });
336 |
337 | /* IPC Event handling
338 | ----------------------*/
339 | /* Logger */
340 | ipcMain.on('logger', function (event, data) {
341 | console.log(data);
342 | }); // Handle console.logs from Renderer
343 | ipcMain.on('get-logger-type', function (event) {
344 | event.returnValue = prefs.logToFile;
345 | }); // Handle Get logger type request
346 |
347 | /* Window controls */
348 | ipcMain.on('app-close', function (event, data) {
349 | if (data) {
350 | mainWindow.hide();
351 | if (isMac) {
352 | app.dock.hide();
353 | }
354 | } else {
355 | app.quit();
356 | }
357 | }); // Close button control
358 | ipcMain.on('app-min', function () {
359 | mainWindow.minimize();
360 | }); // Minimize button control
361 | ipcMain.on('app-max', function () {
362 | mainWindow.isMaximized() ? mainWindow.restore() : mainWindow.maximize();
363 | }); // Maximize/Restore button control
364 | ipcMain.on('show-win', function () {
365 | showWindow();
366 | }); // Show and focus mainWindow
367 | ipcMain.on('drag-enter', function () {
368 | if (!procSearch && !procUpd && !procImport) {
369 | mainWindow.webContents.send('show-drag-ol');
370 | }
371 | }); // Validate 'dragenter' event on mainWindow
372 | ipcMain.on('drag-leave', function () {
373 | if (!procSearch && !procUpd && !procImport) {
374 | mainWindow.webContents.send('hide-ol');
375 | }
376 | }); // Validate 'dragleave' event on mainWindow
377 | ipcMain.on('show-dev', function () {
378 | if (mainWindow) {
379 | mainWindow.openDevTools();
380 | }
381 | }); // Hand show dev console event
382 |
383 | /* App Update */
384 | ipcMain.on('app-upd-check', function (event, data) {
385 | checkAppUpd(data);
386 | }); // Handle check app updates event
387 |
388 | /* Import */
389 | ipcMain.on('pop-import', function () {
390 | popImport();
391 | }); // Import dump file open dialog
392 | ipcMain.on('drop-import', function (event, data) {
393 | if (!procSearch && !procUpd) {
394 | doImport(false, data, '', false); // (isUpd, filePath, timestamp, keepDL)
395 | } else {
396 | popErr('Cannot import in the middle of another process');
397 | }
398 | }); // Import files dragged and dropped to the mainWindow
399 | ipcMain.on('upd-import', function (event, data) {
400 | if (!procSearch) {
401 | doImport(true, data[0], data[1], data[2]);
402 | } else {
403 | popWarn('Can\'t update the dump file in the middle of searching')
404 | }
405 | }); // Import dump file after update is downloaded
406 |
407 | /* Search */
408 | ipcMain.on('search-start', function (event, data) {
409 | initSearch(data[0], data[1], data[2], data[3]);
410 | }); // Handle search event
411 | ipcMain.on('add-history-itm', function (event, data) {
412 | addHistoryItm(data);
413 | }); // Handle add new history item event
414 | ipcMain.on('clear-history', function () {
415 | clearHistory().then(function () {
416 | popSuccess('Search history was cleared successfully');
417 | }).catch(function () {
418 | popErr('Failed to clear search history on DB')
419 | });
420 | }); // Handle clear search history event
421 |
422 | /* Preferences */
423 | ipcMain.on('pref-change', function (event, data) {
424 | prefs[data[0]] = data[1];
425 | }); // Handle any preference change event
426 | ipcMain.on('save-rnd-prefs', function (event, data) {
427 | saveRndPrefs(data).then(function () {
428 | popSuccess('Settings were saved successfully');
429 | }).catch(function () {
430 | popErr('Failed to save settings to the DB')
431 | });
432 | }); // Handle saving of preferences from renderer process
433 | ipcMain.on('save-upd-last', function (event, data) {
434 | saveUpdLast(data[0]).then(function () {
435 | if (data[1] === 'reset') {
436 | popSuccess('Dump update was Reset successfully');
437 | }
438 | }).catch(function () {
439 | switch (data[1]) {
440 | case 'import':
441 | popErr('Failed to update dump Timestamp on DB');
442 | break;
443 | case 'reset':
444 | popErr('Failed to Reset update on DB');
445 | break;
446 | }
447 | });
448 | }); // Handle updLast update event
449 |
450 | /* Scrape */
451 | ipcMain.on('scrape-start', function (event, data) {
452 | initScrape(data[0], data[1]);
453 | }); // Handle seed/peer count event
454 |
455 | /* Trackers */
456 | ipcMain.on('upd-trackers', function () {
457 | updTrackers();
458 | }); // Handle update trackers event
459 |
460 | /* Update dump */
461 | ipcMain.on('upd-dump', function (event, data) {
462 | let type = data[1];
463 | switch (type) {
464 | case 'auto':
465 | case 'check':
466 | case 'notify':
467 | // console.log(type);
468 | checkDumpUpd(type, data[0], data[2]);
469 | break;
470 | case 'user':
471 | initUpdDump(type, data[0]);
472 | break;
473 | case 'tray':
474 | if (mainWindow.isMinimized() || !mainWindow.isVisible()) {
475 | checkDumpUpd('notify', data[0], data[2]);
476 | } else {
477 | checkDumpUpd('check', data[0], data[2]);
478 | }
479 | break;
480 | }
481 | }); // Handle update dump event
482 |
483 | /* Themes */
484 | ipcMain.on('theme-import', function () {
485 | popThemeImport();
486 | }); // Handle import theme event
487 |
488 | /* Import & Export settings */
489 | ipcMain.on('pop-exp-conf', function () {
490 | popExpConf();
491 | }); // Handle export settings save dialog event
492 | ipcMain.on('pop-imp-conf', function () {
493 | popImpConf();
494 | }); // Handle import settings open dialog event
495 |
496 | /* Misc */
497 | ipcMain.on('update-sup-msg', function () {
498 | updSupMsgDate();
499 | }); // Handle 'update support message timestamp' event
500 |
501 | /* Notification senders
502 | ------------------------*/
503 | // Show blue background notification
504 | function popMsg(msg) {
505 | mainWindow.webContents.send('notify', [msg, 'info']);
506 | }
507 | // Show green background notification
508 | function popSuccess(msg) {
509 | mainWindow.webContents.send('notify', [msg, 'success']);
510 | }
511 | // Show red background notification
512 | function popErr(msg) {
513 | mainWindow.webContents.send('notify', [msg, 'danger']);
514 | }
515 | // Show yellow background notification
516 | function popWarn(msg) {
517 | mainWindow.webContents.send('notify', [msg, 'warning']);
518 | }
519 |
520 | /* Misc Functions
521 | ------------------*/
522 | // Initiate the waiting boolean and kill the corresponding child process
523 | function waitProcess(event, _process, name) {
524 | event.preventDefault();
525 | awaitingQuit = true;
526 | popWarn('Wait for background process ' + name + ' to finish');
527 | _process.kill('SIGINT');
528 | }
529 |
530 | // Show splash window
531 | function showSplash() {
532 | splashWindow = new BrowserWindow({
533 | width: 300,
534 | height: 300,
535 | resizable: false,
536 | show: false,
537 | frame: false,
538 | transparent: true,
539 | alwaysOnTop: true,
540 | icon: appIcon,
541 | title: 'OfflineBay by m4heshd'
542 | });
543 |
544 | splashWindow.loadURL(url.format({
545 | pathname: path.join(__dirname, 'WndSplash.html'),
546 | protocol: 'file:',
547 | slashes: true
548 | }));
549 |
550 | splashWindow.webContents.once('dom-ready', function () {
551 | splashWindow.show();
552 | });
553 |
554 | splashWindow.once('show', function () {
555 | splashWindow.webContents.send('fade');
556 | });
557 | }
558 |
559 | // Create the main window and handle events
560 | function startOB() {
561 | let curScreen;
562 |
563 | if (prefs.position[0] !== null && prefs.position[1] !== null) {
564 | curScreen = electron.screen.getDisplayNearestPoint({x: prefs.position[0], y: prefs.position[1]});
565 | } else {
566 | curScreen = electron.screen.getPrimaryDisplay();
567 | }
568 |
569 | if (prefs.size[0] > curScreen.workAreaSize.width) {
570 | prefs.size[0] = curScreen.workAreaSize.width;
571 | prefs.position[0] = curScreen.bounds.x;
572 | }
573 | if (prefs.size[1] > curScreen.workAreaSize.height) {
574 | prefs.size[1] = curScreen.workAreaSize.height;
575 | prefs.position[1] = curScreen.bounds.y;
576 | }
577 |
578 | mainWindow = new BrowserWindow({
579 | width: prefs.size[0],
580 | height: prefs.size[1],
581 | x: prefs.position[0],
582 | y: prefs.position[1],
583 | minWidth: 762,
584 | minHeight: 700,
585 | show: false,
586 | frame: false,
587 | backgroundColor: '#1e2a31',
588 | webPreferences: {
589 | experimentalFeatures: true
590 | },
591 | icon: appIcon,
592 | title: 'OfflineBay by m4heshd'
593 | });
594 |
595 | mainWindow.loadURL(url.format({
596 | pathname: path.join(__dirname, 'WndMain.html'),
597 | protocol: 'file:',
598 | slashes: true
599 | }));
600 |
601 | mainWindow.webContents.once('dom-ready', function () {
602 | if (prefs.maxed) {
603 | mainWindow.maximize();
604 | }
605 | mainWindow.webContents.send('set-version', version);
606 | mainWindow.show();
607 | });
608 |
609 | mainWindow.once('show', function () {
610 | if (splashWindow) {
611 | splashWindow.destroy();
612 | }
613 | });
614 |
615 | mainWindow.on('close', function (event) {
616 | if (procImport) {
617 | waitProcess(event, procImport, '\'IMPORT\'');
618 | } // Validation of any running child processes before closing (Import)
619 | if (procSearch) {
620 | waitProcess(event, procSearch, '\'SEARCH\'');
621 | } // Validation of any running child processes before closing (Search)
622 | if (procScrape) {
623 | waitProcess(event, procScrape, '\'SCRAPE\'');
624 | } // Validation of any running child processes before closing (Scrape)
625 | if (procUpd) {
626 | waitProcess(event, procUpd, '\'UPDATE\'');
627 | } // Validation of any running child processes before closing (Update)
628 | });
629 | mainWindow.on('closed', function () {
630 | mainWindow = null;
631 | app.quit();
632 | });
633 | mainWindow.on('maximize', function () {
634 | mainWindow.webContents.send('maxed');
635 | updatePrefs();
636 | });
637 | mainWindow.on('unmaximize', function () {
638 | mainWindow.webContents.send('restored');
639 | updatePrefs();
640 | });
641 | // Using the following events will ensure that the prefs object is always updated.
642 | // Handling this in the window close event may record incorrect data.
643 | mainWindow.on('move', function () {
644 | updatePrefs();
645 | });
646 | mainWindow.on('resize', function () {
647 | updatePrefs();
648 | });
649 |
650 | setSysTray();
651 | }
652 |
653 | // Show and Focus mainWindow
654 | function showWindow(){
655 | mainWindow.show();
656 | mainWindow.focus();
657 | if (isMac) {
658 | app.dock.show();
659 | }
660 | }
661 |
662 | // Create system tray icon and functions
663 | function setSysTray() {
664 | obTray = new Tray(trayIcon);
665 | const trayMnu = Menu.buildFromTemplate([
666 | {label: 'OfflineBay ' + version, icon: path.join(__dirname, 'img', 'icon_16.png'), enabled: false},
667 | {type: 'separator'},
668 | {label: 'Show', click: showWindow},
669 | {label: 'Center on screen', click: centerWindow},
670 | {label: 'Check dump updates', click: updCheckRequest},
671 | {type: 'separator'},
672 | {label: 'Quit', click: app.quit}
673 | ]);
674 | obTray.setToolTip('OfflineBay');
675 | obTray.setContextMenu(trayMnu);
676 |
677 | obTray.on('click', showWindow);
678 | obTray.on('double-click', showWindow);
679 |
680 | function centerWindow(){
681 | mainWindow.center();
682 | }
683 |
684 | function updCheckRequest() {
685 | mainWindow.webContents.send('upd-check-tray');
686 | }
687 |
688 | if (isMac) {
689 | const dockMnu = Menu.buildFromTemplate([
690 | {label: 'OfflineBay ' + version, icon: path.join(__dirname, 'img', 'icon_16.png'), enabled: false},
691 | {type: 'separator'},
692 | {label: 'Show', click: showWindow},
693 | {label: 'Center on screen', click: centerWindow},
694 | {label: 'Check dump updates', click: updCheckRequest}
695 | ]);
696 | app.dock.setMenu(dockMnu);
697 | }
698 | }
699 |
700 | // Show open dialog for dump imports
701 | function popImport() {
702 | let dlg = dialog.showOpenDialog(
703 | mainWindow,
704 | {
705 | properties: ['openFile'],
706 | title: 'Open dump file (CSV or GZ)',
707 | filters: [
708 | {name: 'Dump Files', extensions: ['csv', 'gz']}
709 | ]
710 | });
711 |
712 | if (typeof dlg !== "undefined") {
713 | doImport(false, dlg[0], '', false);
714 | }
715 | }
716 |
717 | // Perform dump import process (Update or manual)
718 | function doImport(isUpd, filePath, timestamp, keepDL) {
719 | if (!procImport) {
720 | mainWindow.webContents.send('import-start');
721 | procImport = cp.fork(path.join(__dirname, 'main-functions', 'import-dump.js'), [isUpd, filePath, timestamp, keepDL], {
722 | cwd: __dirname,
723 | silent: true
724 | });
725 | procImport.stdout.on('data', function (data) {
726 | console.log(data.toString().slice(0,-1));
727 | });
728 | procImport.on('exit', function () {
729 | console.log('Import process ended');
730 | procImport = null;
731 | if (awaitingQuit) {
732 | process.emit('cont-quit');
733 | }
734 | });
735 | procImport.on('message', function (m) {
736 | mainWindow.webContents.send(m[0], m[1]);
737 | });
738 | } else {
739 | popWarn('One Import process is already running');
740 | }
741 | }
742 |
743 | // Initiate search child process
744 | function initSearch(query, count, smart, inst) {
745 | if (!procSearch) {
746 | procSearch = cp.fork(path.join(__dirname, 'main-functions', 'search.js'), [query, count, smart, inst], {
747 | cwd: __dirname,
748 | silent: true
749 | });
750 | procSearch.stdout.on('data', function (data) {
751 | console.log(data.toString().slice(0,-1));
752 | });
753 | procSearch.on('exit', function () {
754 | console.log('Search process ended');
755 | procSearch = null;
756 | if (awaitingQuit) {
757 | process.emit('cont-quit');
758 | }
759 | });
760 | procSearch.on('message', function (m) {
761 | mainWindow.webContents.send(m[0], m[1]);
762 | });
763 | mainWindow.webContents.send('search-init');
764 | } else {
765 | popWarn('One Search process is already running');
766 | mainWindow.webContents.send('hide-ol');
767 | }
768 | }
769 |
770 | // Initiate tracker scrape child process
771 | function initScrape(hash, isDHT) {
772 | if (!procScrape) {
773 | mainWindow.webContents.send('scrape-init');
774 | procScrape = cp.fork(path.join(__dirname, 'main-functions', 'scrape.js'), [hash, isDHT], {
775 | cwd: __dirname,
776 | silent: true
777 | });
778 | procScrape.stdout.on('data', function (data) {
779 | console.log(data.toString().slice(0,-1));
780 | });
781 | procScrape.on('exit', function () {
782 | console.log('Scraping process ended');
783 | procScrape = null;
784 | if (awaitingQuit) {
785 | process.emit('cont-quit');
786 | }
787 | if (awaitingScrape) {
788 | process.emit('cont-scrape');
789 | awaitingScrape = false;
790 | } else {
791 | mainWindow.webContents.send('scrape-end');
792 | }
793 | });
794 | procScrape.on('message', function (m) {
795 | mainWindow.webContents.send(m[0], m[1]);
796 | });
797 | } else {
798 | awaitingScrape = true;
799 | scrapeOpts = [hash, isDHT];
800 | procScrape.kill('SIGINT');
801 | }
802 | }
803 |
804 | // Start tracker updating process
805 | function updTrackers(){
806 | getTrackerEP().then(function (url) {
807 | request.get(url, function (err, res, body) {
808 | if (err) {
809 | console.log(err);
810 | mainWindow.webContents.send('upd-trackers-failed', 'net');
811 | }
812 | else {
813 | let trcks = body.trim().split('\n\n');
814 | if (trcks.length > 0) {
815 | setTrackers(trcks).then(function () {
816 | mainWindow.webContents.send('upd-trackers-success', trcks);
817 | }).catch(function () {
818 | mainWindow.webContents.send('upd-trackers-failed', 'update');
819 | });
820 | } else {
821 | mainWindow.webContents.send('upd-trackers-failed', 'empty');
822 | }
823 | }
824 | });
825 | }).catch(function () {
826 | mainWindow.webContents.send('upd-trackers-failed', 'ep');
827 | });
828 | }
829 |
830 | /* Dump updates
831 | ----------------*/
832 | // Initialize dump update
833 | function initUpdDump(type, dlURL) {
834 | if (!procSearch && !procImport) {
835 | if (!procUpd) {
836 | procUpd = cp.fork(path.join(__dirname, 'main-functions', 'upd-dump.js'), [type, dlURL], {
837 | cwd: __dirname,
838 | silent: true
839 | });
840 | procUpd.stdout.on('data', function (data) {
841 | console.log(data.toString().slice(0,-1));
842 | });
843 | procUpd.on('exit', function () {
844 | console.log('Dump update process ended');
845 | procUpd = null;
846 | if (awaitingQuit) {
847 | process.emit('cont-quit');
848 | }
849 | });
850 | procUpd.on('message', function (m) {
851 | mainWindow.webContents.send(m[0], m[1]);
852 | });
853 | } else {
854 | if (type === 'user') {
855 | popWarn('One update process is already running');
856 | }
857 | }
858 | } else {
859 | if (type === 'user') {
860 | popWarn('Can\'t update. Dump file is busy at the moment');
861 | }
862 | }
863 | }
864 |
865 | // Check for dump file updates
866 | function checkDumpUpd(type, dlURL, updLast) {
867 | if (!procUpd && !procImport) {
868 | let req = request({
869 | method: 'GET',
870 | uri: dlURL
871 | });
872 |
873 | req.on('response', function (data) {
874 | if ((data.headers['content-type'].split('/')[0]) === 'application') {
875 | let update = new Date(data.headers['last-modified']) - new Date(updLast);
876 | if (update > 0) {
877 | if (type === 'check') {
878 | dialog.showMessageBox(
879 | mainWindow,
880 | {
881 | type: 'question',
882 | buttons: ['Yes', 'No'],
883 | title: 'Update confirmation',
884 | message: 'An update is available. Do you want to proceed with the download?',
885 | cancelId: 1
886 | }, function (res) {
887 | if (res === 0) {
888 | mainWindow.webContents.send('upd-dump-init', 'user');
889 | initUpdDump('user', dlURL);
890 | } else {
891 | mainWindow.webContents.send('hide-ol');
892 | mainWindow.webContents.send('hide-stat');
893 | }
894 | });
895 | } else if (type === 'notify') {
896 | mainWindow.webContents.send('upd-check-notify');
897 | } else if (type === 'auto') {
898 | mainWindow.webContents.send('upd-dump-init', type);
899 | initUpdDump(type, dlURL);
900 | }
901 | } else {
902 | mainWindow.webContents.send('upd-check-unavail', type);
903 | }
904 | } else {
905 | mainWindow.webContents.send('upd-check-failed', ['content', type]);
906 | }
907 | req.abort();
908 | });
909 | req.on('error', function (err) {
910 | console.log(err);
911 | mainWindow.webContents.send('upd-check-failed', ['download', type]);
912 | });
913 | } else {
914 | if (type === 'check') {
915 | popWarn('An update process or import process is already running');
916 | }
917 | }
918 | }
919 |
920 | /* Themes
921 | -----------*/
922 | // Show open dialog to import themes
923 | function popThemeImport() {
924 | let dlg = dialog.showOpenDialog(
925 | mainWindow,
926 | {
927 | properties: ['openFile'],
928 | title: 'Open OfflineBay Theme (ZIP)',
929 | filters: [
930 | {name: 'Theme packages', extensions: ['zip']}
931 | ]
932 | });
933 |
934 | if (typeof dlg !== "undefined") {
935 | mainWindow.webContents.send('init-theme-import', dlg[0]);
936 | }
937 | }
938 |
939 | /* App Updates
940 | ---------------*/
941 | // Check for new OfflineBay versions
942 | function checkAppUpd(updURL) {
943 | request({
944 | method: 'GET',
945 | uri: updURL
946 | }, function (error, response, body) {
947 | if (!error) {
948 | try {
949 | let stable = JSON.parse(body)["stable"];
950 | if (stable > version) {
951 | let link = JSON.parse(body)["stable-dl"];
952 | dialog.showMessageBox(
953 | mainWindow,
954 | {
955 | type: 'question',
956 | buttons: ['Yes', 'No'],
957 | title: 'OfflineBay update',
958 | message: 'You\'re using an old version of OfflineBay. New version ' + stable + ' is available.\nDo you want to visit the download page?',
959 | cancelId: 1
960 | }, function (res) {
961 | if (res === 0) {
962 | mainWindow.webContents.send('open-link', link);
963 | }
964 | });
965 | }
966 | } catch (error) {
967 | console.log(error);
968 | }
969 | } else {
970 | console.log(error);
971 | }
972 | }
973 | );
974 | }
975 |
976 | /* Import & Export settings
977 | ----------------------------*/
978 | // Pop save dialog and export settings
979 | function popExpConf() {
980 | let dlg = dialog.showSaveDialog(
981 | mainWindow,
982 | {
983 | title: 'Export settings as',
984 | defaultPath: 'OBSettings.db',
985 | filters: [
986 | {name: 'Settings dump', extensions: ['db']}
987 | ]
988 | });
989 |
990 | if (typeof dlg !== "undefined") {
991 | try {
992 | fs.createReadStream(path.join(__dirname, 'data', 'config.db')).pipe(fs.createWriteStream(dlg));
993 | popSuccess('Settings were exported successfully');
994 | } catch (error) {
995 | console.log(error);
996 | popErr('Failed to export. An error occurred');
997 | }
998 | }
999 | }
1000 |
1001 | // Pop open dialog, Validate and import settings
1002 | function popImpConf() {
1003 | let dlg = dialog.showOpenDialog(
1004 | mainWindow,
1005 | {
1006 | properties: ['openFile'],
1007 | title: 'Open exported settings (DB)',
1008 | filters: [
1009 | {name: 'Settings dump', extensions: ['db']}
1010 | ]
1011 | });
1012 |
1013 | if (typeof dlg !== "undefined") {
1014 | let expConf = new Datastore({
1015 | filename: dlg[0],
1016 | autoload: true
1017 | });
1018 |
1019 | expConf.findOne({type: 'gen'}, function (err, pref) {
1020 | if (!err && pref) {
1021 | if (prefs.confVer === pref.confVer) {
1022 | config.remove({}, {multi: true}, function (err, numRemoved) {
1023 | if (err || numRemoved < 1) {
1024 | popErr('Failed to import. An error occurred while clearing current settings');
1025 | } else {
1026 | expConf.find({}, function (err, pref) {
1027 | if (!err && pref) {
1028 | config.insert(pref, function (err) {
1029 | if (err) {
1030 | popErr('Failed to import. An error occurred while replacing current settings');
1031 | } else {
1032 | popSuccess('Settings were imported successfully');
1033 | dialog.showMessageBox(
1034 | mainWindow,
1035 | {
1036 | type: 'info',
1037 | buttons: ['Ok'],
1038 | title: 'App restart needed',
1039 | message: 'Import is complete. OfflineBay will restart to apply new settings'
1040 | }, function () {
1041 | app.relaunch();
1042 | app.quit();
1043 | });
1044 | }
1045 | });
1046 | } else {
1047 | popErr('Failed to import. An error occurred while retrieving new settings');
1048 | }
1049 | });
1050 | }
1051 | });
1052 | } else {
1053 | popErr('Unable to import. Exported settings version doesn\'t match the current version');
1054 | }
1055 | } else {
1056 | console.log(err, pref);
1057 | popErr('Unable to import. An error occurred trying to open the file');
1058 | }
1059 | })
1060 | }
1061 | }
1062 |
--------------------------------------------------------------------------------