Compare commits
670 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e579146260 | |||
| 9d18333a93 | |||
| 5daece6dab | |||
| 8bfb7d906e | |||
| f4ece13ceb | |||
| 763ea6ee7a | |||
| cc27cb3459 | |||
| b8c57df4f5 | |||
| e6765380bc | |||
| 0172a003d3 | |||
| ef2b3a04b2 | |||
| 75f5760b33 | |||
| 1f033d1e47 | |||
| 458e21479b | |||
| 46e5324ae6 | |||
| de8996b3b3 | |||
| 1f83848358 | |||
| 69d3ade305 | |||
| a0a1fe90f7 | |||
| 553bf80960 | |||
| 04446b582e | |||
| f10f4a2588 | |||
| 513f566a59 | |||
| f49f083044 | |||
| 830bc9336e | |||
| 3b049219e4 | |||
| bbc68b6a8a | |||
| 0afc01080d | |||
| af1cc4db8e | |||
| 8defa9ee71 | |||
| cb6d9f2b20 | |||
| 9f6c31ffa3 | |||
| 0b3161ca6f | |||
| c79bdff4b8 | |||
| b92957668b | |||
| b1b52e2640 | |||
| a39fa2979c | |||
| e0a74f7979 | |||
| a7b8333710 | |||
| 77850da0fe | |||
| 71f4eeeb25 | |||
| e5eace479d | |||
| 7e927fea10 | |||
| 38cd952b44 | |||
| b07fedeba0 | |||
| 2bbaae5d7d | |||
| 003e9ffccb | |||
| 7a51dbaf5e | |||
| 4fa4878e61 | |||
| daa04d995e | |||
| 74e4bce6bf | |||
| a0aa100a99 | |||
| 4c3b248cc0 | |||
| 96d525832f | |||
| 9255c95ecc | |||
| e117d5925a | |||
| 6a5d7637c2 | |||
| 93e8bdff00 | |||
| a82afbf97b | |||
| e1363d3fe2 | |||
| 1338861dc3 | |||
| b9068559e2 | |||
| b7ed072966 | |||
| e281b71d4e | |||
| 67d26e4b1f | |||
| 1b27980992 | |||
| 292ad15152 | |||
| df1b30d6f4 | |||
| 0d3aa86d31 | |||
| a07f81e20c | |||
| 61365d738c | |||
| 33f13b51b0 | |||
| 2bda3b40eb | |||
| e456ba660b | |||
| 342b2b2069 | |||
| 91697aee42 | |||
| f4e56ca4b7 | |||
| 363cef2959 | |||
| e938f30377 | |||
| ed622ade1f | |||
| 06f0ccf2e5 | |||
| 6885da1b86 | |||
| 3ffce0566b | |||
| a215df0360 | |||
| 6bea4322c1 | |||
| 493df2b333 | |||
| 73c5a6790a | |||
| 644966e438 | |||
| af6e0f33c0 | |||
| 81656a2cd0 | |||
| 74b9f465a1 | |||
| 92177a2c4b | |||
| bc0114a164 | |||
| ff918c916c | |||
| d81c0bbb9a | |||
| b240029a47 | |||
| ba887bcc2b | |||
| 1d90231ebd | |||
| 39d92907e9 | |||
| 4272a53f69 | |||
| 0bbe980d48 | |||
| d2017a6c25 | |||
| e3da125cc7 | |||
| 41a81e7793 | |||
| 11c08b0dc2 | |||
| dbc95965aa | |||
| 26b5662c82 | |||
| bb37d4f328 | |||
| b355bcf623 | |||
| 9076ec08f8 | |||
| 65794cabab | |||
| e3f506ec0c | |||
| 70e7c99ff9 | |||
| bc66511647 | |||
| 002ef830dd | |||
| 06ffe6bd87 | |||
| ecf59583d6 | |||
| 7201f17aed | |||
| b4df97b471 | |||
| 5ea19eb441 | |||
| 8857290a0a | |||
| 1dec09b005 | |||
| d67ba74b44 | |||
| 696d5c7f02 | |||
| 106410cb23 | |||
| 237df6011e | |||
| 20dbeb33ac | |||
| 55e2a42f3d | |||
| 637d5e9a80 | |||
| 9867c578dc | |||
| a8bac652d1 | |||
| 7d0a94ee37 | |||
| 0276c8e5b4 | |||
| e93d6a2c92 | |||
| 8743e8a025 | |||
| 680fdac38b | |||
| e06850b5a2 | |||
| 038511fed4 | |||
| 6b2deb8303 | |||
| a8846eaf92 | |||
| 6039a89441 | |||
| 2bfab8b9c8 | |||
| e6c533e139 | |||
| 1c2fd0de05 | |||
| a91af1032c | |||
| c80102c5f5 | |||
| ff312df849 | |||
| 65bf25dc1a | |||
| 6f35e64c2a | |||
| 4c5bba89ef | |||
| 51b1be7bbc | |||
| 1bf594b369 | |||
| de1db5687b | |||
| 98580fc094 | |||
| 8994312ee0 | |||
| bd9b5eca12 | |||
| 4ed0bba8ba | |||
| 7868a4870b | |||
| 8052aa047e | |||
| 559df6bb8f | |||
| c70a35e083 | |||
| 30817eac86 | |||
| 751efd6b00 | |||
| 7688115bd4 | |||
| 732327f5a3 | |||
| 38fb29382b | |||
| ee05252163 | |||
| 74ba6ec5ab | |||
| 1f5f5dc5f0 | |||
| 6be0afd5ff | |||
| 2c269e3a52 | |||
| 79dea2f4b7 | |||
| 9db5c52f13 | |||
| 81a73557e8 | |||
| 6fc02f752d | |||
| 6d893bd1ce | |||
| 68ba9207f9 | |||
| b01d43cf73 | |||
| ba97bbfa66 | |||
| b8dbc902a3 | |||
| b79ef074dd | |||
| 3ceccea9da | |||
| c591ea9655 | |||
| 3245bbdaa1 | |||
| 60161518c6 | |||
| 1de2acc505 | |||
| a77e62066a | |||
| c6722d141b | |||
| 509c79989b | |||
| 333f01afd3 | |||
| edd4053730 | |||
| 7db1e4ff5b | |||
| a9f5312a90 | |||
| 6d9a640030 | |||
| 74c241f819 | |||
| 3e0df405ee | |||
| 4dd6e948f4 | |||
| c0b7612e3a | |||
| 0486e4b417 | |||
| 160fe9725d | |||
| 577a9b32a1 | |||
| f05994d9f8 | |||
| dec938f2f0 | |||
| 084b5350fd | |||
| e971f86932 | |||
| 0be90dda31 | |||
| 2bc4a8c61e | |||
| 6baef42274 | |||
| f4cc731e63 | |||
| 480039c4cc | |||
| b0242d3d5b | |||
| 58cfd2b873 | |||
| 804f864709 | |||
| 57fb4d95ae | |||
| 8030699e15 | |||
| 1185a81c0c | |||
| aa4941f842 | |||
| 841bd8b923 | |||
| 85ae4cdbca | |||
| c257816608 | |||
| 1b44ef725b | |||
| bffe919b93 | |||
| 87635ade34 | |||
| c6a9eb226d | |||
| da2b6638d9 | |||
| 91072509d3 | |||
| 0d75370f95 | |||
| 994d351998 | |||
| 166aba7747 | |||
| bc3ea97780 | |||
| 2beb6f14a8 | |||
| 5d8566a934 | |||
| 84b002d513 | |||
| a4fb082088 | |||
| 3aa18e7f72 | |||
| e939f9e5dc | |||
| 53ec0b1dee | |||
| fcfd32a71e | |||
| 2ff56a3770 | |||
| 48768f0658 | |||
| 3e84230b33 | |||
| 5490946267 | |||
| 9461381042 | |||
| 364d7a8f66 | |||
| 7a5a1554ca | |||
| 8410be59b2 | |||
| 795db0b926 | |||
| 079cab8544 | |||
| 7109747e00 | |||
| 9b698f5a0e | |||
| 2b35fb9bc2 | |||
| 2b80127ad0 | |||
| d47fa3d705 | |||
| 555db61ec0 | |||
| 7c2fc774ca | |||
| 8eeab3d3fa | |||
| 7a5b16aecc | |||
| c5f72414e2 | |||
| 4016f5c02e | |||
| af7eb1f359 | |||
| f30ed3c8f7 | |||
| e9e86fbc83 | |||
| d7b985bc3f | |||
| e307240a60 | |||
| 8dd1d84485 | |||
| 3c7b0b26e3 | |||
| 778e092c17 | |||
| 6e462166d1 | |||
| 812b419379 | |||
| 04dcddd3e1 | |||
| 1cfec289b7 | |||
| 2dcfb8c25e | |||
| 482b9312b1 | |||
| dbb1889fc9 | |||
| a6de12c875 | |||
| d14ddef8e8 | |||
| c8698e5b80 | |||
| e8c99caf87 | |||
| f70595915d | |||
| c5962041cb | |||
| 28bbf26334 | |||
| 3d6931f8d8 | |||
| 46d686bee5 | |||
| 9ffaf1116f | |||
| 2d4a05f3b0 | |||
| e04d3a582e | |||
| 943830f2ed | |||
| 137927d44a | |||
| 819f86632e | |||
| 06f881932c | |||
| cb845ea65a | |||
| 60e4907be6 | |||
| bc38978d6a | |||
| 90c3bbff13 | |||
| 3116c53734 | |||
| 78fb6ba455 | |||
| 86304c126d | |||
| 3424424e67 | |||
| 7206f2fc3c | |||
| bd25e566c8 | |||
| f54bc32e90 | |||
| f6dde55234 | |||
| 1303713f5e | |||
| 57a5c0c932 | |||
| 22a41c332f | |||
| 76e7223e85 | |||
| 014619ed46 | |||
| aec3a25e3a | |||
| 02f6ffa3cb | |||
| 5d32fc633c | |||
| cde2832ae3 | |||
| 8656fa0862 | |||
| 0cd1ed1e34 | |||
| d58e485846 | |||
| a6aca44672 | |||
| cc28e4b19b | |||
| 599dec1d6e | |||
| d020359058 | |||
| 04d68e293b | |||
| 6b93a80888 | |||
| c3400d936e | |||
| 871be123d4 | |||
| bed2148ba0 | |||
| 8b97c90e88 | |||
| 832befbafd | |||
| ddbf27e2f9 | |||
| 64b30b6c26 | |||
| 32c8767e65 | |||
| 69391c6de0 | |||
| fbec4e7c4d | |||
| 7640ba583d | |||
| 924b397727 | |||
| 565afdde74 | |||
| 3dfd4ec4da | |||
| 0a21f590ff | |||
| 79bf90bb3c | |||
| f4602b4b68 | |||
| 93a6f1cfbd | |||
| f0d14afb7e | |||
| d9a69ae009 | |||
| 0f5398b106 | |||
| c6eb96547a | |||
| 31ab487d82 | |||
| 0b2e271663 | |||
| a4a4b2321f | |||
| ef372da87b | |||
| 10cab1a9a0 | |||
| a4a651b397 | |||
| b2770795ad | |||
| f4c8ff66d5 | |||
| 1b450c8022 | |||
| e5b8846286 | |||
| 8de8591725 | |||
| 20c30279b0 | |||
| 9000be502b | |||
| cfe66cf337 | |||
| 3557d17bb6 | |||
| 2af891aab8 | |||
| 0d276de39b | |||
| 8a6beda335 | |||
| 68f1cdc4de | |||
| 5290fcfa14 | |||
| d865b630a3 | |||
| 9d95d294cd | |||
| ea46d9b3c6 | |||
| ef2c992af9 | |||
| 64ee097c85 | |||
| 31517f93cb | |||
| 6d54e557b9 | |||
| 3e61adcea3 | |||
| 84d9ced137 | |||
| bc45ed9d5b | |||
| 3d98104dbf | |||
| fb6986ff6e | |||
| b8507570c6 | |||
| cf3ef7606c | |||
| 12a9fa92c1 | |||
| 0fd6617eba | |||
| 7ebbf929d5 | |||
| 14223c2204 | |||
| 2cfb6b1914 | |||
| c38cf80589 | |||
| 3ee98eae1d | |||
| d2cff6356a | |||
| 3ee432d683 | |||
| 7f1822bb7e | |||
| 0223e691ff | |||
| 117dba9f37 | |||
| e5241d09d7 | |||
| c9ea1dece2 | |||
| 60d4108ddc | |||
| ffc8de4766 | |||
| 66f114bf72 | |||
| 44f00483f9 | |||
| 1866e3fd4f | |||
| cfac883cbf | |||
| 1431bab366 | |||
| bb373947ff | |||
| 8f0dc65341 | |||
| 402185e1a2 | |||
| d59b5c9841 | |||
| eb442e4a7a | |||
| e82e567069 | |||
| dc2876098d | |||
| 6287942fbc | |||
| 389dc080b6 | |||
| 45a23d73e6 | |||
| 8303dd305b | |||
| a98292ce1e | |||
| 8df97b8433 | |||
| f29e95c9bc | |||
| 52e045c886 | |||
| 4632c3619a | |||
| 54c48be29b | |||
| 301f1403df | |||
| 88de047778 | |||
| c119de78ce | |||
| 9d381e16da | |||
| e4c6d6a9c0 | |||
| 5bb727cb6f | |||
| 00f3209c68 | |||
| 3f8b0e6f5f | |||
| 0543bc4e2c | |||
| 9ded42f127 | |||
| 834a1ad839 | |||
| 8f830f6a0d | |||
| ee111c92ee | |||
| 74c3a3c696 | |||
| 4d964ae16e | |||
| be2c2e8383 | |||
| 4e6164816f | |||
| fa7daf377f | |||
| afbe364525 | |||
| ece5cea512 | |||
| dc8fa5c647 | |||
| 4fd96e7cae | |||
| 1f22b5efba | |||
| 801db0ed38 | |||
| f72577de65 | |||
| dd21c76dea | |||
| 5a5a932735 | |||
| d63c1d431d | |||
| 1a12256c4c | |||
| 9394a0a79b | |||
| 5e66205298 | |||
| eb796c3c5f | |||
| 9d17b3e9b2 | |||
| 7ee8d4fa0f | |||
| dba4bfb0e7 | |||
| f9755f69cd | |||
| 7a68e1bb82 | |||
| 3b12f3960f | |||
| dbe26dfa98 | |||
| 3be8952cff | |||
| dc5a368e00 | |||
| 11aca7ea0b | |||
| c35af2b109 | |||
| b33b505ccb | |||
| dd22cc0306 | |||
| 206eda08aa | |||
| f6e29700d0 | |||
| c17ceb365d | |||
| a1851c6b31 | |||
| 071b0a21c2 | |||
| e25dfb143c | |||
| 652f04bada | |||
| 5962334f3d | |||
| f984b175dc | |||
| ad1979417a | |||
| 16822b9b55 | |||
| 36551da19e | |||
| f3426fdc90 | |||
| 94ead296bf | |||
| 22a6f73e51 | |||
| 94b4c08da9 | |||
| 041b7701c2 | |||
| c20d9b65ee | |||
| 47c5daea81 | |||
| 9676e2a060 | |||
| bb75fa6dcc | |||
| b547ef8f2e | |||
| cc44e21d0e | |||
| 20ac050514 | |||
| 7eeafb6c93 | |||
| 6fd63c872f | |||
| b8b4f04423 | |||
| e3e2abac6f | |||
| b34f3ceb89 | |||
| feb07a43e1 | |||
| 4652672ef4 | |||
| 8b375c019d | |||
| e232e4af70 | |||
| f57d71cebe | |||
| 90daf4cb19 | |||
| d1f759ec44 | |||
| 45ad6dfbda | |||
| 65d45f69fe | |||
| 7e626704ac | |||
| e0f9dce952 | |||
| edf627923e | |||
| 40f7abcc22 | |||
| d837e97d02 | |||
| 13034b4e90 | |||
| b3826032ea | |||
| d5e9ada2c9 | |||
| 724a2a7f76 | |||
| 64326a11af | |||
| b00536a0c7 | |||
| 8885e63474 | |||
| 28936fd13e | |||
| 08a419f209 | |||
| 8a3391c9f4 | |||
| 3777a1f753 | |||
| b81298d6eb | |||
| f67b9b8dd6 | |||
| a303afb9a5 | |||
| eee07b1b7d | |||
| a27c49a376 | |||
| fd81dc5194 | |||
| 394dfdfffc | |||
| 99e9aa8046 | |||
| ec295b3d72 | |||
| 8208440987 | |||
| 47426f2d66 | |||
| ac75a05511 | |||
| c3ff1f3b23 | |||
| 0f3157980c | |||
| 36929f9dc5 | |||
| 11c507008b | |||
| 3d66549849 | |||
| 47ca535268 | |||
| 7d102f9fcb | |||
| edf350adf3 | |||
| 651f28e7e8 | |||
| 4d7acb7c0a | |||
| b75e969a01 | |||
| 0b6f5e7d43 | |||
| a82000a13b | |||
| edee889a1a | |||
| afd2fdeabf | |||
| 34011d7bc8 | |||
| 6cf6b69f8e | |||
| a386c704e4 | |||
| 0994244b0e | |||
| 777977e001 | |||
| af5ac44dc8 | |||
| 23382a9415 | |||
| 7b2da0f35b | |||
| 4037dad582 | |||
| 353ee23cfc | |||
| 29b0d90bc7 | |||
| 073e514f95 | |||
| a35fba4820 | |||
| 828d854eb5 | |||
| 5dbe157170 | |||
| cdc196c2b0 | |||
| 3d26445953 | |||
| f887878b9e | |||
| 908f60df1a | |||
| 56c9b58d87 | |||
| d56983d45d | |||
| 8775e12587 | |||
| df2e01813e | |||
| d77a6faf36 | |||
| 5e735b513d | |||
| 64f0a3db80 | |||
| e470ed00d6 | |||
| 36a27bae46 | |||
| 5d900806e8 | |||
| ace14c85dc | |||
| 3ec4bd26e6 | |||
| b9bc5e6c07 | |||
| 021fbc243a | |||
| 8b66f2ea6e | |||
| 7703c28c6e | |||
| 26db88ec51 | |||
| 51b7ea4030 | |||
| 1e2368dd3b | |||
| f28c3ae5c5 | |||
| ebdd125d91 | |||
| 9bcc386269 | |||
| 27c48af278 | |||
| 4b4df884dc | |||
| 64082ddd70 | |||
| 188a39d0d2 | |||
| 44f8f23a7c | |||
| 32340218e2 | |||
| 85a8430c3f | |||
| e47816e273 | |||
| b7356702fc | |||
| a61c857e11 | |||
| d3ef30d943 | |||
| b8d5eb5ac1 | |||
| aeaf0fe9ca | |||
| 8bebb942ca | |||
| f0e795f00c | |||
| 7d4eec6b02 | |||
| fe2dc7a541 | |||
| 848b9e78e1 | |||
| 2fa33f75e5 | |||
| 1f6c2a6ff0 | |||
| a8e3c7843c | |||
| 8b1ba2425c | |||
| 47acf4fad1 | |||
| 9beb48be92 | |||
| bbe52a2a18 | |||
| 0b73028412 | |||
| 2311a7ed86 | |||
| 95ed300d1b | |||
| dcd2d50c83 | |||
| 57ed6c7941 | |||
| 870c4089fb | |||
| 1082f9330e | |||
| a7f33869a0 | |||
| 89336dda37 | |||
| 4240dee426 | |||
| 83c1c1340e | |||
| 408e33adb1 | |||
| d2dde274d3 | |||
| 7b27f82f1e | |||
| 2d32a2848c | |||
| c128689b2e | |||
| 7702aa6ca1 | |||
| 2fb32fa347 | |||
| 2744db2d75 | |||
| adb060b076 | |||
| 561d7608d8 | |||
| 06d9a95827 | |||
| 9c386c4bdc | |||
| a1eb48926a | |||
| c1ed827244 | |||
| 663e1a2db1 | |||
| c0a2b50b9f | |||
| a843ae4553 | |||
| d195b23512 | |||
| f6c84b7e57 | |||
| 98df9c3f2a | |||
| 7e703eb552 | |||
| 87bcac6122 | |||
| 6935a12289 | |||
| 39688b667e | |||
| f174887f9f | |||
| caba086651 | |||
| b627b38985 | |||
| 1c35d72aa5 | |||
| 8ecbfdd66d | |||
| 3be568f6ab | |||
| fa29c11c73 | |||
| 6b153d68b4 | |||
| 852266dc16 | |||
| 4170cd3419 | |||
| a15870871f | |||
| 571499caad | |||
| 766c2c26b8 | |||
| 1f27f88aa7 | |||
| e06888e998 | |||
| 91a2d0a270 | |||
| 8e3fe4c980 | |||
| f3724107f0 | |||
| 580c32a2b9 | |||
| 575712ba9f | |||
| 84006f348c | |||
| a9759441d3 | |||
| 728f8e9019 | |||
| e814210896 | |||
| a0c6877524 | |||
| 29b0dd627d | |||
| dc48cad7e8 | |||
| 35df1c799b | |||
| 912e661c02 |
@@ -2,19 +2,11 @@
|
||||
"comments": false,
|
||||
"env": {
|
||||
"main": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"targets": { "node": 7 }
|
||||
}],
|
||||
"stage-0"
|
||||
]
|
||||
"presets": ["@babel/preset-env"]
|
||||
},
|
||||
"renderer": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}],
|
||||
"stage-0"
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
@@ -27,12 +19,7 @@
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false
|
||||
}],
|
||||
"stage-0"
|
||||
],
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [
|
||||
[
|
||||
"component",
|
||||
@@ -44,5 +31,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"plugins": ["transform-runtime"]
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ process.env.NODE_ENV = 'production'
|
||||
const { say } = require('cfonts')
|
||||
const chalk = require('chalk')
|
||||
const del = require('del')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const Multispinner = require('multispinner')
|
||||
|
||||
const Webpack = require('webpack')
|
||||
const Multispinner = require('@motrix/multispinner')
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
const rendererConfig = require('./webpack.renderer.config')
|
||||
@@ -19,12 +17,16 @@ const errorLog = chalk.bgRed.white(' ERROR ') + ' '
|
||||
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
|
||||
const isCI = process.env.CI || false
|
||||
|
||||
if (process.env.BUILD_TARGET === 'clean') clean()
|
||||
else if (process.env.BUILD_TARGET === 'web') web()
|
||||
else build()
|
||||
if (process.env.BUILD_TARGET === 'clean') {
|
||||
clean()
|
||||
} else if (process.env.BUILD_TARGET === 'web') {
|
||||
web()
|
||||
} else {
|
||||
build()
|
||||
}
|
||||
|
||||
function clean () {
|
||||
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
|
||||
del.sync(['release/*', '!.gitkeep'])
|
||||
console.log(`\n${doneLog}\n`)
|
||||
process.exit()
|
||||
}
|
||||
@@ -73,9 +75,10 @@ function build () {
|
||||
function pack (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
config.mode = 'production'
|
||||
webpack(config, (err, stats) => {
|
||||
if (err) reject(err.stack || err)
|
||||
else if (stats.hasErrors()) {
|
||||
Webpack(config, (err, stats) => {
|
||||
if (err) {
|
||||
reject(err.stack || err)
|
||||
} else if (stats.hasErrors()) {
|
||||
let err = ''
|
||||
|
||||
stats.toString({
|
||||
@@ -99,9 +102,9 @@ function pack (config) {
|
||||
}
|
||||
|
||||
function web () {
|
||||
del.sync(['dist/web/*', '!.gitkeep'])
|
||||
deleteSync(['dist/web/*', '!.gitkeep'])
|
||||
webConfig.mode = 'production'
|
||||
webpack(webConfig, (err, stats) => {
|
||||
Webpack(webConfig, (err, stats) => {
|
||||
if (err || stats.hasErrors()) console.log(err)
|
||||
|
||||
console.log(stats.toString({
|
||||
@@ -117,16 +120,20 @@ function greeting () {
|
||||
const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 85) text = 'lets-build'
|
||||
else if (cols > 60) text = 'lets-|build'
|
||||
else text = false
|
||||
if (cols > 85) {
|
||||
text = 'lets-build'
|
||||
} else if (cols > 60) {
|
||||
text = 'lets-|build'
|
||||
} else {
|
||||
text = false
|
||||
}
|
||||
|
||||
if (text && !isCI) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
colors: ['magentaBright'],
|
||||
font: 'simple3d',
|
||||
space: false
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n lets-build'))
|
||||
} else console.log(chalk.magentaBright.bold('\n lets-build'))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
'use strict'
|
||||
|
||||
const chalk = require('chalk')
|
||||
const electron = require('electron')
|
||||
const path = require('path')
|
||||
const path = require('node:path')
|
||||
const { spawn } = require('node:child_process')
|
||||
const { say } = require('cfonts')
|
||||
const { spawn } = require('child_process')
|
||||
const webpack = require('webpack')
|
||||
const electron = require('electron')
|
||||
const chalk = require('chalk')
|
||||
const Webpack = require('webpack')
|
||||
const WebpackDevServer = require('webpack-dev-server')
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware')
|
||||
|
||||
const mainConfig = require('./webpack.main.config')
|
||||
const rendererConfig = require('./webpack.renderer.config')
|
||||
|
||||
let electronProcess = null
|
||||
let manualRestart = false
|
||||
let hotMiddleware
|
||||
|
||||
function logStats (proc, data) {
|
||||
let log = ''
|
||||
@@ -39,41 +37,22 @@ function logStats (proc, data) {
|
||||
}
|
||||
|
||||
function startRenderer () {
|
||||
return new Promise((resolve, reject) => {
|
||||
rendererConfig.entry.index = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.index)
|
||||
return new Promise(async (resolve, reject) => {
|
||||
rendererConfig.entry.index = rendererConfig.entry.index
|
||||
rendererConfig.mode = 'development'
|
||||
const compiler = webpack(rendererConfig)
|
||||
hotMiddleware = webpackHotMiddleware(compiler, {
|
||||
log: false,
|
||||
heartbeat: 2500
|
||||
})
|
||||
|
||||
compiler.hooks.compilation.tap('compilation', compilation => {
|
||||
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
const compiler = Webpack(rendererConfig)
|
||||
const devServerOptions = {
|
||||
...rendererConfig.devServer,
|
||||
port: 9080,
|
||||
static: {
|
||||
directory: path.resolve(__dirname, "../"),
|
||||
},
|
||||
};
|
||||
|
||||
compiler.hooks.done.tap('done', stats => {
|
||||
logStats('Renderer', stats)
|
||||
})
|
||||
|
||||
const server = new WebpackDevServer(
|
||||
compiler,
|
||||
{
|
||||
contentBase: path.join(__dirname, '../'),
|
||||
quiet: true,
|
||||
before (app, ctx) {
|
||||
app.use(hotMiddleware)
|
||||
ctx.middleware.waitUntilValid(() => {
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
server.listen(9080)
|
||||
const server = new WebpackDevServer(devServerOptions, compiler)
|
||||
await server.start()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,11 +60,11 @@ function startMain () {
|
||||
return new Promise((resolve, reject) => {
|
||||
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
|
||||
mainConfig.mode = 'development'
|
||||
const compiler = webpack(mainConfig)
|
||||
const compiler = Webpack(mainConfig)
|
||||
|
||||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
|
||||
logStats('Main', chalk.white.bold('compiling...'))
|
||||
hotMiddleware.publish({ action: 'compiling' })
|
||||
// hotMiddleware.publish({ action: 'compiling' })
|
||||
done()
|
||||
})
|
||||
|
||||
@@ -149,17 +128,21 @@ function greeting () {
|
||||
const cols = process.stdout.columns
|
||||
let text = ''
|
||||
|
||||
if (cols > 104) text = 'electron-vue'
|
||||
else if (cols > 76) text = 'electron-|vue'
|
||||
else text = false
|
||||
if (cols > 104) {
|
||||
text = 'motrix-dev'
|
||||
} else if (cols > 76) {
|
||||
text = 'motrix-|dev'
|
||||
} else {
|
||||
text = false
|
||||
}
|
||||
|
||||
if (text) {
|
||||
say(text, {
|
||||
colors: ['yellow'],
|
||||
colors: ['magentaBright'],
|
||||
font: 'simple3d',
|
||||
space: false
|
||||
})
|
||||
} else console.log(chalk.yellow.bold('\n electron-vue'))
|
||||
} else console.log(chalk.magentaBright.bold('\n motrix-dev'))
|
||||
console.log(chalk.blue(' getting ready...') + '\n')
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
process.env.BABEL_ENV = 'main'
|
||||
|
||||
const path = require('node:path')
|
||||
const Webpack = require('webpack')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const { dependencies } = require('../package.json')
|
||||
const { appId } = require('../electron-builder.json')
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies, build } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
|
||||
let mainConfig = {
|
||||
entry: {
|
||||
@@ -18,17 +19,6 @@ let mainConfig = {
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js)$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
@@ -50,7 +40,10 @@ let mainConfig = {
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -59,7 +52,15 @@ let mainConfig = {
|
||||
},
|
||||
extensions: ['.js', '.json', '.node']
|
||||
},
|
||||
target: 'electron-main'
|
||||
target: 'electron-main',
|
||||
optimization: {
|
||||
minimize: !devMode,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
})
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,9 +68,9 @@ let mainConfig = {
|
||||
*/
|
||||
if (devMode) {
|
||||
mainConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
|
||||
'appId': `"${build.appId}"`
|
||||
'appId': `"${appId}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -79,10 +80,9 @@ if (devMode) {
|
||||
*/
|
||||
if (!devMode) {
|
||||
mainConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
'appId': `"${build.appId}"`
|
||||
'appId': `"${appId}"`
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
process.env.BABEL_ENV = 'renderer'
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const path = require('node:path')
|
||||
const Webpack = require('webpack')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const { dependencies } = require('../package.json')
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
|
||||
/**
|
||||
* List of node_modules to include in webpack bundle
|
||||
@@ -24,7 +24,6 @@ const { VueLoaderPlugin } = require('vue-loader')
|
||||
let whiteListedModules = ['vue']
|
||||
|
||||
let rendererConfig = {
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
entry: {
|
||||
index: path.join(__dirname, '../src/renderer/pages/index/main.js')
|
||||
},
|
||||
@@ -34,14 +33,10 @@ let rendererConfig = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|vue)$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
loader: 'worker-loader',
|
||||
options: { filename: '[name].js' }
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -52,8 +47,11 @@ let rendererConfig = {
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
data: '@import "@/components/Theme/Variables.scss";',
|
||||
includePaths:[__dirname, 'src']
|
||||
implementation: require('sass'),
|
||||
additionalData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -66,9 +64,12 @@ let rendererConfig = {
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
indentedSyntax: true,
|
||||
data: '@import "@/components/Theme/Variables.scss";',
|
||||
includePaths:[__dirname, 'src']
|
||||
additionalData: '@import "@/components/Theme/Variables.scss";',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -88,10 +89,6 @@ let rendererConfig = {
|
||||
'css-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: 'vue-html-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
@@ -117,31 +114,15 @@ let rendererConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
},
|
||||
{
|
||||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'media/[name]--[folder].[ext]'
|
||||
}
|
||||
type: 'asset/resource'
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name]--[folder].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -155,12 +136,6 @@ let rendererConfig = {
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css'
|
||||
}),
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: {
|
||||
safe: true,
|
||||
discardComments: { removeAll: true }
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Motrix',
|
||||
filename: 'index.html',
|
||||
@@ -171,17 +146,25 @@ let rendererConfig = {
|
||||
// removeAttributeQuotes: true,
|
||||
// removeComments: true
|
||||
// },
|
||||
isBrowser: false,
|
||||
isDev: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: devMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
new Webpack.HotModuleReplacementPlugin(),
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
],
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: path.join(__dirname, '../dist/electron')
|
||||
path: path.join(__dirname, '../dist/electron'),
|
||||
globalObject: 'this',
|
||||
publicPath: ''
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -191,15 +174,26 @@ let rendererConfig = {
|
||||
},
|
||||
extensions: ['.js', '.vue', '.json', '.css', '.node']
|
||||
},
|
||||
target: 'electron-renderer'
|
||||
target: 'electron-renderer',
|
||||
optimization: {
|
||||
minimize: !devMode,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust rendererConfig for development settings
|
||||
*/
|
||||
if (devMode) {
|
||||
rendererConfig.devtool = 'eval-cheap-module-source-map'
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
|
||||
})
|
||||
)
|
||||
@@ -209,21 +203,18 @@ if (devMode) {
|
||||
* Adjust rendererConfig for production settings
|
||||
*/
|
||||
if (!devMode) {
|
||||
rendererConfig.devtool = ''
|
||||
|
||||
rendererConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
from: path.join(__dirname, '../static'),
|
||||
to: path.join(__dirname, '../dist/electron/static'),
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
globOptions: { ignore: [ '.*' ] }
|
||||
}]
|
||||
}),
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
new Webpack.LoaderOptionsPlugin({
|
||||
minimize: false
|
||||
})
|
||||
)
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
process.env.BABEL_ENV = 'web'
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
const path = require('path')
|
||||
const path = require('node:path')
|
||||
const { dependencies } = require('../package.json')
|
||||
const webpack = require('webpack')
|
||||
|
||||
const BabiliWebpackPlugin = require('babili-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const Webpack = require('webpack')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
||||
const ESLintPlugin = require('eslint-webpack-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const TerserPlugin = require('terser-webpack-plugin')
|
||||
const devMode = process.env.NODE_ENV !== 'production'
|
||||
|
||||
/**
|
||||
* List of node_modules to include in webpack bundle
|
||||
@@ -24,7 +24,6 @@ const { VueLoaderPlugin } = require('vue-loader')
|
||||
let whiteListedModules = ['vue']
|
||||
|
||||
let webConfig = {
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
entry: {
|
||||
index: path.join(__dirname, '../src/renderer/pages/index/main.js')
|
||||
},
|
||||
@@ -34,14 +33,10 @@ let webConfig = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|vue)$/,
|
||||
enforce: 'pre',
|
||||
exclude: /node_modules/,
|
||||
test: /\.worker\.js$/,
|
||||
use: {
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
loader: 'worker-loader',
|
||||
options: { filename: '[name].js' }
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -49,7 +44,16 @@ let webConfig = {
|
||||
use: [
|
||||
devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
additionalData: '@import "@/components/Theme/Variables.scss"',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -57,7 +61,17 @@ let webConfig = {
|
||||
use: [
|
||||
devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'sass-loader?indentedSyntax'
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
implementation: require('sass'),
|
||||
indentedSyntax: true,
|
||||
additionalData: '@import "@/components/Theme/Variables.scss"',
|
||||
sassOptions: {
|
||||
includePaths:[__dirname, 'src']
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -97,23 +111,11 @@ let webConfig = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'imgs/[name].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
query: {
|
||||
limit: 10000,
|
||||
name: 'fonts/[name].[ext]'
|
||||
}
|
||||
}
|
||||
type: 'asset/inline'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -123,12 +125,6 @@ let webConfig = {
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css'
|
||||
}),
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: {
|
||||
safe: true,
|
||||
discardComments: { removeAll: true }
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'Motrix',
|
||||
filename: 'index.html',
|
||||
@@ -139,19 +135,27 @@ let webConfig = {
|
||||
// removeAttributeQuotes: true,
|
||||
// removeComments: true
|
||||
// },
|
||||
isBrowser: true,
|
||||
isDev: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: devMode
|
||||
? path.resolve(__dirname, '../node_modules')
|
||||
: false
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.IS_WEB': 'true'
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
new Webpack.HotModuleReplacementPlugin(),
|
||||
new Webpack.NoEmitOnErrorsPlugin(),
|
||||
new ESLintPlugin({
|
||||
extensions: ['js', 'vue'],
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
})
|
||||
],
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.join(__dirname, '../dist/web')
|
||||
path: path.join(__dirname, '../dist/web'),
|
||||
globalObject: 'this',
|
||||
publicPath: ''
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -161,28 +165,41 @@ let webConfig = {
|
||||
},
|
||||
extensions: ['.js', '.vue', '.json', '.css']
|
||||
},
|
||||
target: 'web'
|
||||
target: 'web',
|
||||
optimization: {
|
||||
minimize: !devMode,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
extractComments: false,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust webConfig for development settings
|
||||
*/
|
||||
if (devMode) {
|
||||
webConfig.devtool = 'eval-cheap-module-source-map'
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust webConfig for production settings
|
||||
*/
|
||||
if (!devMode) {
|
||||
webConfig.devtool = ''
|
||||
|
||||
webConfig.plugins.push(
|
||||
new BabiliWebpackPlugin(),
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{
|
||||
from: path.join(__dirname, '../static'),
|
||||
to: path.join(__dirname, '../dist/web/static'),
|
||||
ignore: ['.*']
|
||||
}
|
||||
]),
|
||||
new webpack.DefinePlugin({
|
||||
to: path.join(__dirname, '../dist/electron/static'),
|
||||
globOptions: { ignore: [ '.*' ] }
|
||||
}]
|
||||
}),
|
||||
new Webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
new Webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
src/renderer/components/Icons/*.js
|
||||
|
||||
src/shared/locales/*
|
||||
!src/shared/locales/all.js
|
||||
!src/shared/locales/app.js
|
||||
!src/shared/locales/index.js
|
||||
!src/shared/locales/LocalManager.js
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
extends: 'standard',
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
globals: {
|
||||
appId: true,
|
||||
__static: true
|
||||
},
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
'rules': {
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 0,
|
||||
// allow async-await
|
||||
'generator-star-spacing': 0,
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||
}
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
indent: ['error', 2],
|
||||
'vue/script-indent': ['error', 2, {
|
||||
baseIndent: 1
|
||||
}]
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.vue'],
|
||||
rules: {
|
||||
indent: 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 🐛 [NEW] Bug Report
|
||||
description: File a bug report here
|
||||
title: "[BUG]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report 🤗
|
||||
Make sure there aren't any open/closed issues for this topic 😃
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Description of the bug
|
||||
description: Give us a brief description of what happened and what should have happened
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: app-version
|
||||
attributes:
|
||||
label: Motrix Version
|
||||
description: Please provide detailed version information and installation method, such as macOS Apple silicon dmg, Windows Universal installation file, etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
Run this command in your project's root folder and paste the result:
|
||||
|
||||
```sh
|
||||
npx envinfo --system --binaries --browsers
|
||||
```
|
||||
add `| pbcopy` if you're in macOS for easy copy paste.
|
||||
Alternatively, you can manually gather the version information from your environment.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
More info: A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) is **required**, otherwise the issue might be closed without further notice. [**Why & How?**](https://antfu.me/posts/why-reproductions-are-required)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: |
|
||||
Provide any additional information such as logs, screenshots, likes, scenarios in which the bug occurs so that it facilitates resolving the issue.
|
||||
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you do the following
|
||||
options:
|
||||
- label: Follow our [Code of Conduct](https://github.com/agalwood/Motrix/blob/master/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
|
||||
required: true
|
||||
- label: Check that this is a concrete bug. For Q&A, please open a [GitHub Discussion](https://github.com/agalwood/Motrix/discussions) instead.
|
||||
required: true
|
||||
- label: The provided reproduction is a [minimal reproducible](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
|
||||
required: true
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 🐛 [新] Bug 报告
|
||||
description: 在这里提交 Bug 报告
|
||||
title: "[BUG]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您抽出时间来填写这份 Bug 报告 🤗
|
||||
请确保此问题没有已存在的开放/关闭问题 😃
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Bug 描述
|
||||
description: 给我们一个简短的描述,说明发生了什么以及应该发生什么。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: app-version
|
||||
attributes:
|
||||
label: Motrix 版本
|
||||
description: 请提供详细的版本信息以及安装的方式,如 macOS Apple silicon dmg、Windows Universal 安装文件等
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 环境
|
||||
description: |
|
||||
在项目的根目录下运行以下命令,并将结果粘贴到下方:
|
||||
|
||||
```sh
|
||||
npx envinfo --system --binaries --browsers
|
||||
```
|
||||
在 macOS 中,如果您需要轻松复制粘贴,可以添加 `| pbcopy`。
|
||||
或者,您也可以手动收集您的环境版本信息。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: 复现该问题的步骤。
|
||||
placeholder: |
|
||||
1. 前往 '...'
|
||||
2. 点击 '...'
|
||||
3. 滚动到 '...'
|
||||
4. 查看错误
|
||||
更多信息:[最小复现例子](https://stackoverflow.com/help/minimal-reproducible-example) 是必需的,否则该问题可能会被关闭而没有进一步的通知。[**为什么 & 如何?**](https://antfu.me/posts/why-reproductions-are-required)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: 额外信息
|
||||
description: |
|
||||
提供任何额外的信息,例如日志、截图、喜欢、发生该 Bug 的场景,以便有助于解决问题。
|
||||
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: 验证
|
||||
description: 在提交问题之前,请确保您完成了以下操作
|
||||
options:
|
||||
- label: 遵循我们的[行为准则](https://github.com/agalwood/Motrix/blob/master/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- label: 确认是否已经有一个报告了相同的 Bug,以避免创建重复的问题。
|
||||
required: true
|
||||
- label: 确认此问题是一个具体的 Bug。若要进行问答,请开启 [GitHub 讨论](https://github.com/agalwood/Motrix/discussions)。
|
||||
required: true
|
||||
- label: 提供的复现是该 Bug 的 [最小复现例子](https://stackoverflow.com/help/minimal-reproducible-example)。
|
||||
required: true
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Before feedback**
|
||||
Before you feedback, please search for the issues to see if there are similar problems that can solve your problem.
|
||||
|
||||
**Please delete the above and the contents of this line, then fill in the feedback form in the following format, Thanks.**
|
||||
<!-- Windows and Linux versions hide the application menu by default. Please close the "Hide Menu Bar" in Preferences - Advanced Settings - Appearance. After saving and applying, the menu bar will appear. -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS & Version: [e.g. macOS, Windows, Linux]
|
||||
- Version: [e.g. macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix Version: [e.g. v1.1.3, v1.1.0]
|
||||
- Installation package type: [e.g. dmg, AppImage]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
name: 错误反馈
|
||||
about: 创建一个错误报告帮助改进「请按照模板提交,提供详细的信息,方便我们复现之后处理」
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
反馈之前请搜索一下已有 issues 和 帮助文档,看是否有类似问题可以解决你的问题
|
||||
https://github.com/agalwood/Motrix/issues
|
||||
http://motrix.app/support
|
||||
|
||||
按以下格式填写反馈信息,谢谢
|
||||
-->
|
||||
|
||||
**错误描述**
|
||||
清楚简洁地描述错误,方便我们复现之后处理。
|
||||
|
||||
**如何重现**
|
||||
重现步骤,如:
|
||||
1. 点击新建任务按钮
|
||||
2. 黏贴链接(如链接不涉及到隐私和版权问题,请顺便提供)
|
||||
3. 点击提交
|
||||
4. 发现报错
|
||||
|
||||
**预期的行为**
|
||||
清楚简洁地描述你期望发生的事情。
|
||||
|
||||
**截图**
|
||||
请添加屏幕截图以帮助解释你的问题:
|
||||
打开应用菜单中的「帮助」——「开发者工具」—— 切换到 console,然后**完整**截图。
|
||||
<!-- Windows 和 Linux 版本默认隐藏了应用菜单,请到偏好设置-进阶设置-外观里关闭“隐藏菜单栏”,保存并应用之后菜单栏就出现了 -->
|
||||
|
||||
**运行环境**
|
||||
- 操作系统类型: [如 macOS, Windows, Linux]
|
||||
- 具体版本: [如 macOS 10.14.2, Windows 10, Ubuntu 18.04]
|
||||
- Motrix 版本: [如 v1.1.3, v1.1.0]
|
||||
- 安装包类型:[如 dmg, AppImage]
|
||||
|
||||
**更多信息**
|
||||
补充有关该问题的其他信息。
|
||||
@@ -0,0 +1,16 @@
|
||||
<!-- You can erase any parts of this template not applicable to your Pull Request. -->
|
||||
|
||||
## Description
|
||||
<!-- Write a brief description of the changes introduced by this PR -->
|
||||
|
||||
## Related Issues
|
||||
<!--
|
||||
Link to the issue that is fixed by this PR (if there is one)
|
||||
e.g. Fixes #1234, Addresses #1234, Related to #1234, etc.
|
||||
-->
|
||||
|
||||
### Checklist:
|
||||
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../../pulls) for the same update/change?
|
||||
* [ ] Have you linted your code locally prior to submission?
|
||||
* [ ] Have you successfully ran app with your changes locally?
|
||||
@@ -1,7 +1,7 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 30
|
||||
daysUntilLock: 60
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '23 8 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Build/release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Platforms to build on/for
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Snapcraft
|
||||
uses: samuelmeuli/action-snapcraft@v2
|
||||
# Only install Snapcraft on Ubuntu
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
env:
|
||||
# Snapcraft
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
||||
|
||||
- name: Test Snapcraft
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: snapcraft --help
|
||||
|
||||
- name: Build/release Electron app
|
||||
uses: motrixapp/action-electron-builder@v2
|
||||
with:
|
||||
build_script_name: 'build:github'
|
||||
# GitHub token, automatically provided to the action
|
||||
# (No need to define this secret in the repo settings)
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
# macOS code signing certificate
|
||||
mac_certs: ${{ secrets.mac_certs }}
|
||||
mac_certs_password: ${{ secrets.mac_certs_password }}
|
||||
|
||||
# If the commit is tagged with a version (e.g. "v1.0.0"),
|
||||
# release the app after building
|
||||
release: ${{ vars.skip_publish != 'true' }}
|
||||
env:
|
||||
# Snapcraft
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
||||
# macOS notarization
|
||||
TEAM_ID: ${{ secrets.team_id }}
|
||||
APPLE_ID: ${{ secrets.apple_id }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.apple_app_specific_password }}
|
||||
@@ -1,12 +1,25 @@
|
||||
!.gitkeep
|
||||
.DS_Store
|
||||
dist/electron/*
|
||||
dist/web/*
|
||||
.env
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
*.log
|
||||
node_modules/
|
||||
thumbs.db
|
||||
|
||||
# npm package
|
||||
.npmrc
|
||||
npm-debug.log.*
|
||||
|
||||
# Eslint Cache
|
||||
.eslintcache*
|
||||
|
||||
# electron builder
|
||||
*.provisionprofile
|
||||
build/*.plist
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
dist/electron/*
|
||||
dist/web/*
|
||||
|
||||
# release
|
||||
release/*
|
||||
.idea/
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
osx_image: xcode10.1
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
language: c
|
||||
matrix:
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode11.3
|
||||
- os: linux
|
||||
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||
compiler: clang
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- "$HOME/.electron"
|
||||
- "$HOME/.cache"
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgnome-keyring-dev
|
||||
- icnsutils
|
||||
- rpm
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
|
||||
|
||||
install:
|
||||
- nvm install 10
|
||||
- nvm install 12.14.1
|
||||
- source ~/.bashrc
|
||||
- npm install -g xvfb-maybe
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run release
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Motrix 贡献指南
|
||||
|
||||
开始贡献之前,确保你已经理解了 [GitHub 的协作流程](https://guides.github.com/introduction/flow/)。
|
||||
|
||||
## 🌍 翻译指南
|
||||
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://electronjs.org/docs/api/locales)
|
||||
首先你要确定一个语言的英文简写作为 **locale**,如 en-US,这个 locale 值请严格参考 [Electron 的 Locales 文档](https://www.electronjs.org/docs/api/app#appgetlocale) 和 [Chromium 源代码](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc)。
|
||||
|
||||
Motrix 的国际化分两部分:
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Motrix Contributing Guide
|
||||
|
||||
Before you start contributing, make sure you already understand [GitHub flow](https://guides.github.com/introduction/flow/).
|
||||
|
||||
## 🌍 Translation Guide
|
||||
|
||||
First you need to determine the English abbreviation of a language as **locale**, such as en-US, this locale value should strictly refer to the [electron's documentation](https://electronjs.org/docs/api/locales).
|
||||
First you need to determine the English abbreviation of a language as **locale**, such as en-US, this locale value should strictly refer to the [Electron's Documentation](https://www.electronjs.org/docs/api/app#appgetlocale) and [Chromium Source Code](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc).
|
||||
|
||||
The internationalization of Motrix is divided into two parts:
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
Copyright 2018 Dr_rOot
|
||||
The MIT License
|
||||
|
||||
Copyright 2018-present Dr_rOot
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# Motrix
|
||||
|
||||
<a href="https://motrix.app">
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
|
||||
[English](./README.md) | 简体中文
|
||||
<p>
|
||||
<a href="https://motrix.app">
|
||||
<img src="./static/512x512.png" width="256" alt="Motrix App Icon" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 一款全能的下载工具
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases) [](https://travis-ci.org/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
[](https://github.com/agalwood/Motrix/releases)   
|
||||
|
||||
[English](./README.md) | 简体中文
|
||||
|
||||
我是个兴趣使然的桌面应用开发者🤓,利用搬砖之余开发了 Motrix。
|
||||
|
||||
Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链、百度网盘等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链等资源。它的界面简洁易用,希望大家喜欢 👻。
|
||||
|
||||
✈️ 去 [官网](https://motrix.app/zh-CN) 逛逛 | 📖 查看 [帮助手册](http://motrix.app/support/issues)
|
||||
|
||||
@@ -24,6 +26,20 @@ Motrix 是一款全能的下载工具,支持下载 HTTP、FTP、BT、磁力链
|
||||
|
||||
建议使用安装包(Motrix-Setup-x.y.z.exe)安装 Motrix 以确保完整的体验,例如关联 torrent 文件,捕获磁力链等。
|
||||
|
||||
如果你在 Windows 是用包管理工具来管理应用,如 [Chocolatey](https://chocolatey.org)、[scoop](https://github.com/lukesampson/scoop),你可以使用它们安装 Motrix。
|
||||
|
||||
#### Chocolatey
|
||||
感谢 [@Yato](https://github.com/iYato) 持续维护着 [Motrix Chocolatey](https://community.chocolatey.org/packages/motrix) 包。要安装 Motrix,请从 `命令行` 或 `PowerShell` 中运行以下命令:
|
||||
|
||||
```bash
|
||||
# 安装
|
||||
choco install motrix
|
||||
|
||||
# 升级
|
||||
choco upgrade motrix
|
||||
```
|
||||
|
||||
#### scoop
|
||||
如果你更喜欢便携版,你可以使用 [scoop](https://github.com/lukesampson/scoop)(需要 Windows 7+,天朝用户可能需要设置 Git 代理)安装最新便携版本的 Motrix。
|
||||
|
||||
```bash
|
||||
@@ -33,45 +49,96 @@ scoop install motrix
|
||||
|
||||
### macOS
|
||||
|
||||
macOS 用户可以使用 `brew cask` 安装 Motrix,感谢 [Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
|
||||
macOS 用户可以使用 `brew` 安装 Motrix,感谢 [@Mitscherlich](https://github.com/Mitscherlich) 的 [PR](https://github.com/Homebrew/homebrew-cask/pull/59494)。
|
||||
|
||||
```bash
|
||||
brew update && brew cask install motrix
|
||||
brew update && brew install motrix
|
||||
```
|
||||
|
||||
#### 自动更新
|
||||
Motrix v1.8.0+ 版本更改了应用 BundleID ( `net.agalwood.Motrix` => `app.motrix.native` ), Motrix v1.6.11 的自动更新会因为签名不一致而失败。[Motrix 安装助手](https://github.com/motrixapp/motrix-install-assistant)将帮助您安装最新的 Motrix 应用程序。
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/motrixapp/motrix-install-assistant">
|
||||
<img src="https://raw.githubusercontent.com/motrixapp/motrix-install-assistant/main/build/256x256.png" width="192" alt="Motrix Install Assistant Icon" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### Linux
|
||||
|
||||
你可以下载 AppImage(适用于所有 Linux 发行版)软件包或 snap 或从源代码构建安装 Motrix。
|
||||
你可以下载 `AppImage` (适用于所有 Linux 发行版)或 `snap` 来安装 Motrix,更多 Linux 安装包格式请查看 [GitHub/release](https://github.com/agalwood/Motrix/releases) 。
|
||||
|
||||
构建请阅读 **编译打包** 部分。
|
||||
Motrix 在 Linux 中首次启动可能需要使用 `sudo` 运行,因为可能没有创建下载会话文件的权限 (`/var/cache/aria2.session`)。
|
||||
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [weearc](https://github.com/weearc)。
|
||||
如果你想自己通过编译源码来安装,请阅读 **编译打包** 部分。
|
||||
|
||||
#### AppImage
|
||||
最新版的 Motrix AppImage 需要自己手动进执行桌面集成。请查看 [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) 的文档进行操作。
|
||||
|
||||
> 桌面集成
|
||||
> electron-builder v21 之后,桌面集成不再是 AppImage 文件的一部分。
|
||||
> 推荐使用 [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) 集成 AppImage。
|
||||
|
||||
Deepin 20 Beta 用户安装 Motrix 失败的问题,请按照以下方法处理:
|
||||
|
||||
打开`终端`,黏贴运行如下命令之后再次安装 Motrix。
|
||||
```bash
|
||||
sudo apt --fix-broken install
|
||||
```
|
||||
|
||||
#### Snap
|
||||
Motrix 已经上架 [Snapcraft](https://snapcraft.io/motrix) ,Ubuntu 用户推荐从 Snap 商店下载。
|
||||
|
||||
v1.5.10 提示
|
||||
|
||||
系统托盘可能无法正常显示指示器,导致退出应用程序不方便。
|
||||
请取消勾选 偏好设置——基本设置——隐藏应用程序菜单(仅限Windows和Linux),点击保存并应用。然后点击 "文件 "菜单中的 "退出",退出应用程序。
|
||||
|
||||
请更新到 v1.5.12 及以上版本,可以使用键盘组合快捷键 <kbd>Ctrl</kbd> + <kbd>q</kbd> 快速退出应用。
|
||||
|
||||
#### AUR
|
||||
对于 Arch Linux 用户,可以使用 [aur](https://aur.archlinux.org/packages/motrix/) 安装 Motrix,感谢维护者 [@weearc](https://github.com/weearc)。
|
||||
|
||||
运行以下命令进行安装:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
yay -S motrix
|
||||
```
|
||||
|
||||
#### Flatpak
|
||||
感谢 [@proletarius101](https://github.com/proletarius101) 的 [PR](https://github.com/flathub/flathub/pull/2334),Motrix 已经上架 [Flathub](https://flathub.org/apps/details/net.agalwood.Motrix),喜欢 Flatpak 的 Linux 用户可以尝试。
|
||||
|
||||
```bash
|
||||
# 安装
|
||||
flatpak install flathub net.agalwood.Motrix
|
||||
|
||||
# 运行
|
||||
flatpak run net.agalwood.Motrix
|
||||
```
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 🕹 简洁明了的图形操作界面
|
||||
- 🦄 支持BT和磁力链任务
|
||||
- 💾 支持下载百度云盘资源
|
||||
- ☑️ 支持选择性下载BT部分文件
|
||||
- 📡 每天自动更新 Tracker 服务器列表
|
||||
- 🔌 UPnP & NAT-PMP 端口映射
|
||||
- 🎛 最高支持 10 个任务同时下载
|
||||
- 🚀 单任务最高支持 64 线程下载
|
||||
- 🚥 设置上传/下载限速
|
||||
- 🕶 模拟用户代理UA
|
||||
- 🔔 下载完成后通知
|
||||
- 💻 支持触控栏快捷键 (Mac 专享)
|
||||
- 🤖 常驻系统托盘,操作更加便捷
|
||||
- 📟 系统托盘速度仪表显示实时速度 (Mac 专享)
|
||||
- 🌑 深色模式
|
||||
- 🗑 移除任务时可同时删除相关文件
|
||||
- 🌍 国际化,[查看已可选的语言](#-国际化)
|
||||
- 🎏 ...
|
||||
- 🛠 更多特性开发中
|
||||
|
||||
## 🖥 应用界面
|
||||
|
||||

|
||||

|
||||
|
||||
## ⌨️ 本地开发
|
||||
|
||||
@@ -85,38 +152,45 @@ git clone git@github.com:agalwood/Motrix.git
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
yarn
|
||||
```
|
||||
|
||||
天朝大陆用户建议使用淘宝的 npm 源
|
||||
|
||||
```bash
|
||||
npm config set registry 'https://registry.npm.taobao.org'
|
||||
yarn config set registry 'https://registry.npmmirror.com'
|
||||
npm config set registry 'https://registry.npmmirror.com'
|
||||
export ELECTRON_MIRROR='https://npm.taobao.org/mirrors/electron/'
|
||||
export SASS_BINARY_SITE='https://npm.taobao.org/mirrors/node-sass'
|
||||
```
|
||||
|
||||
如果喜欢 [Yarn](https://yarnpkg.com/),也可以使用 `yarn` 安装依赖
|
||||
> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again
|
||||
|
||||
`Electron` 下载安装失败的问题,解决方式请参考 https://github.com/electron/electron/issues/8466#issuecomment-571425574
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
### 编译打包
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
yarn run build
|
||||
```
|
||||
#### 编译 Apple Silicon 版本
|
||||
|
||||
```bash
|
||||
yarn run build:applesilicon
|
||||
```
|
||||
完成之后可以在项目的 `release` 目录看到编译打包好的应用文件
|
||||
|
||||
## 🛠 技术栈
|
||||
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)
|
||||
- [Aria2](https://aria2.github.io/) (注:macOS 和 Linux 版本使用的是 64 位的 aria2c,Windows 版使用的 32 位的)
|
||||
- [Aria2](https://aria2.github.io/)
|
||||
|
||||
## ☑️ TODO
|
||||
|
||||
@@ -132,16 +206,32 @@ npm run build
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| de | German | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| ar | Arabic | ✔️ [@hadialqattan](https://github.com/hadialqattan), [@AhmedElTabarani](https://github.com/AhmedElTabarani) |
|
||||
| bg | Българският език | ✔️ [@null-none](https://github.com/null-none) |
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| el | Ελληνικά | ✔️ [@Likecinema](https://github.com/Likecinema) |
|
||||
| en-US | English | ✔️ |
|
||||
| es | Español | ✔️ [@Chofito](https://github.com/Chofito)|
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| hu | Hungarian | ✔️ [@zalnaRs](https://github.com/zalnaRs) |
|
||||
| id | Indonesia | ✔️ [@aarestu](https://github.com/aarestu) |
|
||||
| it | Italiano | ✔️ [@blackcat-917](https://github.com/blackcat-917) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| nb | Norsk Bokmål | ✔️ [@rubjo](https://github.com/rubjo) |
|
||||
| nl | Nederlands | ✔️ [@nickbouwhuis](https://github.com/nickbouwhuis) |
|
||||
| pl | Polski | ✔️ [@KanarekLife](https://github.com/KanarekLife) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ro | Română | ✔️ [@alyn3d](https://github.com/alyn3d) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| th | แบบไทย | ✔️ [@nxanywhere](https://github.com/nxanywhere) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| vi | Tiếng Việt | ✔️ [@duythanhvn](https://github.com/duythanhvn) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) [@5idereal](https://github.com/5idereal) |
|
||||
|
||||
## 📜 开源许可
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# Motrix
|
||||
|
||||
<a href="https://motrix.app">
|
||||
<img src="https://cdn.nlark.com/yuque/0/2018/png/129147/1543735425232-a5d2c99f-d788-43e4-9781-558ff6d21027.png" width="256" alt="App Icon" />
|
||||
</a>
|
||||
<p>
|
||||
<a href="https://motrix.app">
|
||||
<img src="./static/512x512.png" width="256" alt="Motrix App Icon" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## A full-featured download manager
|
||||
|
||||
[](https://github.com/agalwood/Motrix/releases) [](https://travis-ci.org/agalwood/Motrix) [](https://ci.appveyor.com/project/agalwood/motrix/branch/master) [](https://github.com/agalwood/Motrix/releases) 
|
||||
[](https://github.com/agalwood/Motrix/releases)   
|
||||
|
||||
English | [简体中文](./README-CN.md)
|
||||
|
||||
Motrix is a full-featured download manager that supports downloading HTTP, FTP, BitTorrent, Magnet, Baidu Net Disk, etc.
|
||||
Motrix is a full-featured download manager that supports downloading HTTP, FTP, BitTorrent, Magnet, etc.
|
||||
|
||||
Motrix has a clean and easy to use interface. I hope you will like it 👻.
|
||||
|
||||
@@ -24,6 +26,20 @@ Download from [GitHub Releases](https://github.com/agalwood/Motrix/releases) and
|
||||
|
||||
It is recommended to install Motrix using the installation package (Motrix-Setup-x.y.z.exe) to ensure a complete experience, such as associating torrent files, capturing magnet links, etc.
|
||||
|
||||
If you use package management tools to manage applications on Windows, such as [Chocolatey](https://chocolatey.org), [scoop](https://github.com/lukesampson/scoop). You can use them to install Motrix.
|
||||
|
||||
#### Chocolatey
|
||||
Thanks to [@Yato](https://github.com/iYato) for continuing to maintain the [Motrix Chocolatey](https://community.chocolatey.org/packages/motrix) package. To install motrix, run the following command from the `command line` or from `PowerShell`:
|
||||
|
||||
```bash
|
||||
# Install
|
||||
choco install motrix
|
||||
|
||||
# Upgrade
|
||||
choco upgrade motrix
|
||||
```
|
||||
|
||||
#### scoop
|
||||
If you prefer the portable version, you can use [scoop](https://github.com/lukesampson/scoop) (need Windows 7+) to install Motrix.
|
||||
|
||||
```bash
|
||||
@@ -33,45 +49,99 @@ scoop install motrix
|
||||
|
||||
### macOS
|
||||
|
||||
The macOS users can install Motrix using `brew cask`, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [Mitscherlich](https://github.com/Mitscherlich).
|
||||
The macOS users can install Motrix using `brew`, thanks to [PR](https://github.com/Homebrew/homebrew-cask/pull/59494) of [@Mitscherlich](https://github.com/Mitscherlich).
|
||||
|
||||
```bash
|
||||
brew update && brew cask install motrix
|
||||
brew update && brew install motrix
|
||||
```
|
||||
|
||||
#### Auto Update
|
||||
|
||||
Since Motrix v1.8.0 and later versions changed the App BundleID ( `net.agalwood.Motrix` => `app.motrix.native` ), the automatic update of Motrix v1.6.11 will fail. [Motrix Install Assistant](https://github.com/motrixapp/motrix-install-assistant) will help you install the latest Motrix application.
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/motrixapp/motrix-install-assistant">
|
||||
<img src="https://raw.githubusercontent.com/motrixapp/motrix-install-assistant/main/build/256x256.png" width="192" alt="Motrix Install Assistant Icon" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### Linux
|
||||
|
||||
You can download the AppImage (for all Linux distributions) package or snap or just build from source code to install Motrix.
|
||||
You can download the `AppImage` (for all Linux distributions) or `snap` to install Motrix, see [GitHub/release](https://github.com/agalwood/Motrix/releases) for more Linux installation package formats.
|
||||
|
||||
Please read the **Build** section.
|
||||
Motrix may need to run with `sudo` for the first time in Linux because there is no permission to create the download session file (`/var/cache/aria2.session`).
|
||||
|
||||
For Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [weearc](https://github.com/weearc).
|
||||
If you want to build from source code, please read the **Build** section.
|
||||
|
||||
#### AppImage
|
||||
The latest version of Motrix AppImage requires you to manually perform desktop integration. Please check the documentation of [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) .
|
||||
|
||||
> Desktop Integration
|
||||
> Since electron-builder 21 desktop integration is not a part of produced AppImage file.
|
||||
> [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) is the recommended way to integrate AppImages.
|
||||
|
||||
Deepin 20 Beta users failed to install Motrix, please follow the steps below:
|
||||
|
||||
Open the `Terminal`, paste and run the following command to install Motrix again.
|
||||
|
||||
```bash
|
||||
sudo apt --fix-broken install
|
||||
```
|
||||
|
||||
#### Snap
|
||||
Motrix has been listed on [Snapcraft](https://snapcraft.io/motrix) , Ubuntu users recommend downloading from the Snap Store.
|
||||
|
||||
Tips for v1.5.10
|
||||
|
||||
The tray may not display the indicator normally, which makes it inconvenient to exit the application.
|
||||
|
||||
Please unchecked Preferences--Basic Settings--Hide App Menu (Windows & Linux Only), click Save & Apply. Then click "Exit" in the File menu to exit the application.
|
||||
|
||||
Please update to v1.5.12 and above, you can use the keyboard shortcut <kbd>Ctrl</kbd> + <kbd>q</kbd> to quickly exit the application.
|
||||
|
||||
#### AUR
|
||||
For Arch Linux users, Motrix is available in [aur](https://aur.archlinux.org/packages/motrix/), thanks to the maintainer [@weearc](https://github.com/weearc).
|
||||
|
||||
Run the following command to install:
|
||||
|
||||
```bash
|
||||
yay motrix
|
||||
yay -S motrix
|
||||
```
|
||||
|
||||
#### Flatpak
|
||||
Thanks to the [PR](https://github.com/flathub/flathub/pull/2334) of [@proletarius101](https://github.com/proletarius101), Motrix has been listed [Flathub](https://flathub.org/apps/details/net.agalwood.Motrix), Linux users who like the Flatpak can try it.
|
||||
|
||||
```bash
|
||||
# Install
|
||||
flatpak install flathub net.agalwood.Motrix
|
||||
|
||||
# Run
|
||||
flatpak run net.agalwood.Motrix
|
||||
```
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🕹 Simple and clear user interface
|
||||
- 🦄 Supports BitTorrent & Magnet
|
||||
- 💾 Supports downloading Baidu Net Disk
|
||||
- ☑️ BitTorrent selective download
|
||||
- 📡 Update tracker list every day automatically
|
||||
- 🔌 UPnP & NAT-PMP Port Mapping
|
||||
- 🎛 Up to 10 concurrent download tasks
|
||||
- 🚀 Supports 64 threads in a single task
|
||||
- 🚥 Supports speed limit
|
||||
- 🕶 Mock User-Agent
|
||||
- 🔔 Download completed Notification
|
||||
- 💻 Ready for Touch Bar (Mac only)
|
||||
- 🤖 Resident system tray for quick operation
|
||||
- 📟 Tray speed meter displays real-time speed (Mac only)
|
||||
- 🌑 Dark mode
|
||||
- 🗑 Delete related files when removing tasks (optional)
|
||||
- 🌍 I18n, [View supported languages](#-internationalization).
|
||||
- 🎏 ...
|
||||
- 🛠 More features in development
|
||||
|
||||
## 🖥 User Interface
|
||||
|
||||

|
||||

|
||||
|
||||
## ⌨️ Development
|
||||
|
||||
@@ -85,21 +155,28 @@ git clone git@github.com:agalwood/Motrix.git
|
||||
|
||||
```bash
|
||||
cd Motrix
|
||||
npm install
|
||||
yarn
|
||||
```
|
||||
|
||||
If you like [Yarn](https://yarnpkg.com/), you can also use `yarn` to install dependencies.
|
||||
> Error: Electron failed to install correctly, please delete node_modules/electron and try installing again
|
||||
|
||||
`Electron` failed to install correctly, please refer to https://github.com/electron/electron/issues/8466#issuecomment-571425574
|
||||
|
||||
### Dev Mode
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
### Build Release
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
yarn run build
|
||||
```
|
||||
#### Build for Apple Silicon
|
||||
|
||||
```bash
|
||||
yarn run build:applesilicon
|
||||
```
|
||||
|
||||
After building, the application will be found in the project's `release` directory.
|
||||
@@ -108,7 +185,7 @@ After building, the application will be found in the project's `release` directo
|
||||
|
||||
- [Electron](https://electronjs.org/)
|
||||
- [Vue](https://vuejs.org/) + [VueX](https://vuex.vuejs.org/) + [Element](https://element.eleme.io)
|
||||
- [Aria2](https://aria2.github.io/) (Note: macOS and Linux versions use 64-bit aria2c, Windows version uses 32-bit)
|
||||
- [Aria2](https://aria2.github.io/)
|
||||
|
||||
## ☑️ TODO
|
||||
|
||||
@@ -124,16 +201,32 @@ Translations into versions for other languages are welcome 🧐! Please read the
|
||||
|
||||
| Key | Name | Status |
|
||||
|-------|:--------------------|:-------------|
|
||||
| de | German | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| ar | Arabic | ✔️ [@hadialqattan](https://github.com/hadialqattan), [@AhmedElTabarani](https://github.com/AhmedElTabarani) |
|
||||
| bg | Българският език | ✔️ [@null-none](https://github.com/null-none) |
|
||||
| ca | Català | ✔️ [@marcizhu](https://github.com/marcizhu) |
|
||||
| de | Deutsch | ✔️ [@Schloemicher](https://github.com/Schloemicher) |
|
||||
| el | Ελληνικά | ✔️ [@Likecinema](https://github.com/Likecinema) |
|
||||
| en-US | English | ✔️ |
|
||||
| es | Español | ✔️ [@Chofito](https://github.com/Chofito)|
|
||||
| fa | فارسی | ✔️ [@Nima-Ra](https://github.com/Nima-Ra) |
|
||||
| fr | Français | ✔️ [@gpatarin](https://github.com/gpatarin) |
|
||||
| hu | Hungarian | ✔️ [@zalnaRs](https://github.com/zalnaRs) |
|
||||
| id | Indonesia | ✔️ [@aarestu](https://github.com/aarestu) |
|
||||
| it | Italiano | ✔️ [@blackcat-917](https://github.com/blackcat-917) |
|
||||
| ja | 日本語 | ✔️ [@hbkrkzk](https://github.com/hbkrkzk) |
|
||||
| ko | 한국어 | ✔️ [@KOZ39](https://github.com/KOZ39) |
|
||||
| nb | Norsk Bokmål | ✔️ [@rubjo](https://github.com/rubjo) |
|
||||
| nl | Nederlands | ✔️ [@nickbouwhuis](https://github.com/nickbouwhuis) |
|
||||
| pl | Polski | ✔️ [@KanarekLife](https://github.com/KanarekLife) |
|
||||
| pt-BR | Portuguese (Brazil) | ✔️ [@andrenoberto](https://github.com/andrenoberto) |
|
||||
| ro | Română | ✔️ [@alyn3d](https://github.com/alyn3d) |
|
||||
| ru | Русский | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| th | แบบไทย | ✔️ [@nxanywhere](https://github.com/nxanywhere) |
|
||||
| tr | Türkçe | ✔️ [@abdullah](https://github.com/abdullah) |
|
||||
| uk | Українська | ✔️ [@bladeaweb](https://github.com/bladeaweb) |
|
||||
| vi | Tiếng Việt | ✔️ [@duythanhvn](https://github.com/duythanhvn) |
|
||||
| zh-CN | 简体中文 | ✔️ |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) |
|
||||
| zh-TW | 繁體中文 | ✔️ [@Yukaii](https://github.com/Yukaii) [@5idereal](https://github.com/5idereal) |
|
||||
|
||||
## 📜 License
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
provider: generic
|
||||
url: 'https://dl.motrix.app/release/'
|
||||
url: 'https://dl.motrix.app/releases/'
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
version: 1.0.{build}
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
image: Visual Studio 2017
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- '%APPDATA%\npm-cache'
|
||||
- '%USERPROFILE%\.electron'
|
||||
- '%USERPROFILE%\AppData\Local\Yarn\cache'
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 10 x64
|
||||
- ps: Install-Product node 12.14.1 x64
|
||||
- git reset --hard HEAD
|
||||
- npm install
|
||||
- node --version
|
||||
@@ -27,3 +20,7 @@ build_script:
|
||||
- npm run release
|
||||
|
||||
test: off
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 103 KiB |
@@ -0,0 +1,94 @@
|
||||
// Forked from https://github.com/samuelmeuli/mini-diary/blob/master/scripts/after-pack.js
|
||||
|
||||
/**
|
||||
* Source: https://github.com/patrikx3/redis-ui/blob/master/src/build/after-pack.js
|
||||
*
|
||||
* Copyright (c) 2019 Patrik Laszlo / P3X / Corifeus and contributors.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// TODO: Remove script once https://github.com/electron/electron/issues/17972 is solved by
|
||||
// `electron-builder`
|
||||
|
||||
const fs = require('node:fs')
|
||||
const { spawn } = require('node:child_process')
|
||||
const { chdir } = require('node:process')
|
||||
|
||||
const pkg = require('../package.json')
|
||||
const binName = `${pkg.name}`.toLowerCase()
|
||||
|
||||
const exec = async function exec (cmd, args = []) {
|
||||
const child = spawn(cmd, args, { shell: true })
|
||||
redirectOutputFor(child)
|
||||
await waitFor(child)
|
||||
}
|
||||
|
||||
const redirectOutputFor = child => {
|
||||
const printStdout = data => {
|
||||
process.stdout.write(data.toString())
|
||||
}
|
||||
const printStderr = data => {
|
||||
process.stderr.write(data.toString())
|
||||
}
|
||||
child.stdout.on('data', printStdout)
|
||||
child.stderr.on('data', printStderr)
|
||||
|
||||
child.once('close', () => {
|
||||
child.stdout.off('data', printStdout)
|
||||
child.stderr.off('data', printStderr)
|
||||
})
|
||||
}
|
||||
|
||||
const waitFor = async function (child) {
|
||||
return new Promise(resolve => {
|
||||
child.once('close', () => resolve())
|
||||
})
|
||||
}
|
||||
|
||||
const linuxTargets = [
|
||||
'AppImage',
|
||||
'deb',
|
||||
'rpm',
|
||||
'snap'
|
||||
]
|
||||
|
||||
module.exports = async function (context) {
|
||||
console.warn('after build; disable sandbox')
|
||||
const isLinux = context.targets.find(
|
||||
target => linuxTargets.includes(target)
|
||||
)
|
||||
if (!isLinux) {
|
||||
return
|
||||
}
|
||||
const originalDir = process.cwd()
|
||||
const dirname = context.appOutDir
|
||||
chdir(dirname)
|
||||
|
||||
await exec('mv', [binName, binName + '.bin'])
|
||||
const wrapperScript = `#!/bin/bash
|
||||
"\${BASH_SOURCE%/*}"/${binName}.bin "$@" --no-sandbox
|
||||
`
|
||||
fs.writeFileSync(binName, wrapperScript)
|
||||
await exec('chmod', ['+x', binName])
|
||||
|
||||
chdir(originalDir)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
require('dotenv').config()
|
||||
const { join } = require('node:path')
|
||||
const { notarize } = require('@electron/notarize')
|
||||
const { appId } = require('../electron-builder.json')
|
||||
|
||||
exports.default = async function (context) {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
return
|
||||
}
|
||||
|
||||
const skipNotarize = process.env.SKIP_NOTARIZE
|
||||
if (skipNotarize === 'true') {
|
||||
console.log('Skipping notarize')
|
||||
return
|
||||
}
|
||||
|
||||
const appBundleId = appId
|
||||
const appName = context.packager.appInfo.productFilename
|
||||
const appPath = join(appOutDir, `${appName}.app`)
|
||||
|
||||
try {
|
||||
await notarize({
|
||||
tool: 'notarytool',
|
||||
appBundleId,
|
||||
appPath,
|
||||
teamId: process.env.TEAM_ID,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
console.log(`Done notarizing ${appId}`)
|
||||
}
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 530 KiB |
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"productName": "Motrix",
|
||||
"appId": "app.motrix.native",
|
||||
"afterPack": "./build/afterPackHook.js",
|
||||
"afterSign": "./build/afterSignHook.js",
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": "torrent",
|
||||
"mimeType": "application/x-bittorrent",
|
||||
"name": "Torrent",
|
||||
"role": "Viewer"
|
||||
}
|
||||
],
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Magnet Protocol",
|
||||
"schemes": [
|
||||
"magnet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Thunder Protocol",
|
||||
"schemes": [
|
||||
"thunder"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dmg": {
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 380
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 230,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 230,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"universal"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"universal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"type": "development",
|
||||
"darkModeSupport": true,
|
||||
"hardenedRuntime": false,
|
||||
"notarize": false,
|
||||
"extraResources": {
|
||||
"from": "./extra/darwin/${arch}/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "appx",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/win32/${arch}/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"artifactName": "${productName}-Setup-${version}.${ext}",
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"appx": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"applicationId": "app.motrix.native",
|
||||
"identityName": "59744DrrOot.Motrix",
|
||||
"publisher": "CN=5BB4961D-30D8-4993-9ADF-05E1E1F5A395",
|
||||
"publisherDisplayName": "Dr_rOot"
|
||||
},
|
||||
"portable": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"mimeTypes": [
|
||||
"application/x-bittorrent",
|
||||
"x-scheme-handler/magnet"
|
||||
],
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64",
|
||||
"armv7l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "rpm",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "snap",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/linux/${arch}/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "https://dl.motrix.app/releases/"
|
||||
},
|
||||
{
|
||||
"provider": "github"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# aria2
|
||||
|
||||
Source code: https://github.com/agalwood/aria2
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix macOS Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=none
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -1,122 +0,0 @@
|
||||
## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
|
||||
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释 ##
|
||||
## 添加了@和默认启用的选项都是系统需要调用的,请不要随意改动否则可能无法正常运行
|
||||
|
||||
## 文件保存相关 ##
|
||||
|
||||
# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
|
||||
# 此项 OS X 无法使用 $HOME 及 ~/ 设置路径 建议使用 /users/用户名/downloads
|
||||
#@dir=$HOME/downloads
|
||||
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
|
||||
#@disk-cache=32M
|
||||
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
# file-allocation=none
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
## 下载连接相关 ##
|
||||
|
||||
# 最大同时下载任务数, 运行时可修改, 默认:5
|
||||
#@max-concurrent-downloads=10
|
||||
# 同一服务器连接数, 添加时可指定, 默认:1
|
||||
#@max-connection-per-server=15
|
||||
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
|
||||
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
|
||||
#@min-split-size=10M
|
||||
# 单个任务最大线程数, 添加时可指定, 默认:5
|
||||
#@split=15
|
||||
# 整体下载速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-download-limit=0
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
max-overall-upload-limit=128K
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
disable-ipv6=false
|
||||
#运行覆盖已存在文件
|
||||
#@allow-overwrite=true
|
||||
#自动重命名
|
||||
#@auto-file-renaming=true
|
||||
|
||||
## 进度保存相关 ##
|
||||
|
||||
# 从会话文件中读取下载任务
|
||||
#@input-file=/Users/Shared/aria2.session
|
||||
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
|
||||
#@save-session=/Users/Shared/aria2.session
|
||||
# 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
|
||||
save-session-interval=30
|
||||
|
||||
## RPC相关设置 ##
|
||||
|
||||
# 启用RPC, 默认:false
|
||||
enable-rpc=true
|
||||
# 允许所有来源, 默认:false
|
||||
rpc-allow-origin-all=true
|
||||
# 允许非外部访问, 默认:false
|
||||
rpc-listen-all=true
|
||||
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
|
||||
#event-poll=select
|
||||
# RPC监听端口, 端口被占用时可以修改, 默认:6800
|
||||
# 使用本客户端请勿修改此项
|
||||
# rpc-listen-port=6800
|
||||
# 设置的RPC授权令牌, v1.18.4新增功能, 取代 --rpc-user 和 --rpc-passwd 选项
|
||||
#rpc-secret=token
|
||||
|
||||
## BT/PT下载相关 ##
|
||||
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=50101-50109
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
enable-dht=true
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
enable-dht6=true
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
dht-listen-port=50101-50109
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
# BT校验相关, 默认:true
|
||||
#bt-hash-check-seed=true
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
bt-seed-unverified=true
|
||||
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
|
||||
bt-save-metadata=false
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
# To select files, use --select-file option. If it is not used,
|
||||
# all files are assumed to be selected. Please use this option with care
|
||||
# because it will actually remove files from your disk. Default: false
|
||||
bt-remove-unselected-file=true
|
||||
# Verify the peer using certificates specified
|
||||
# in --ca-certificate option. Default: true
|
||||
check-certificate=false
|
||||
# Exclude seed only downloads when counting concurrent active downloads (See -j option).
|
||||
# This means that if -j3 is given and this option is turned on and 3 downloads are active and one of those enters seed mode,
|
||||
# then it is excluded from active download count (thus it becomes 2),
|
||||
# and the next download waiting in queue gets started.
|
||||
# But be aware that seeding item is still recognized as active download in RPC method. Default: false
|
||||
bt-detach-seed-only=true
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix macOS Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=none
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Linux Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Linux Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -1,122 +0,0 @@
|
||||
## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
|
||||
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释 ##
|
||||
## 添加了@和默认启用的选项都是系统需要调用的,请不要随意改动否则可能无法正常运行
|
||||
|
||||
## 文件保存相关 ##
|
||||
|
||||
# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
|
||||
# 此项 OS X 无法使用 $HOME 及 ~/ 设置路径 建议使用 /users/用户名/downloads
|
||||
#@dir=$HOME/downloads
|
||||
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
|
||||
#@disk-cache=32M
|
||||
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
file-allocation=trunc
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
## 下载连接相关 ##
|
||||
|
||||
# 最大同时下载任务数, 运行时可修改, 默认:5
|
||||
#@max-concurrent-downloads=10
|
||||
# 同一服务器连接数, 添加时可指定, 默认:1
|
||||
#@max-connection-per-server=15
|
||||
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
|
||||
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
|
||||
#@min-split-size=10M
|
||||
# 单个任务最大线程数, 添加时可指定, 默认:5
|
||||
#@split=15
|
||||
# 整体下载速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-download-limit=0
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
max-overall-upload-limit=128K
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
disable-ipv6=false
|
||||
#运行覆盖已存在文件
|
||||
#@allow-overwrite=true
|
||||
#自动重命名
|
||||
#@auto-file-renaming=true
|
||||
|
||||
## 进度保存相关 ##
|
||||
|
||||
# 从会话文件中读取下载任务
|
||||
#@input-file=/Users/Shared/aria2.session
|
||||
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
|
||||
#@save-session=/Users/Shared/aria2.session
|
||||
# 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
|
||||
save-session-interval=30
|
||||
|
||||
## RPC相关设置 ##
|
||||
|
||||
# 启用RPC, 默认:false
|
||||
enable-rpc=true
|
||||
# 允许所有来源, 默认:false
|
||||
rpc-allow-origin-all=true
|
||||
# 允许非外部访问, 默认:false
|
||||
rpc-listen-all=true
|
||||
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
|
||||
#event-poll=select
|
||||
# RPC监听端口, 端口被占用时可以修改, 默认:6800
|
||||
# 使用本客户端请勿修改此项
|
||||
# rpc-listen-port=6800
|
||||
# 设置的RPC授权令牌, v1.18.4新增功能, 取代 --rpc-user 和 --rpc-passwd 选项
|
||||
#rpc-secret=token
|
||||
|
||||
## BT/PT下载相关 ##
|
||||
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=50101-50109
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
enable-dht=true
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
enable-dht6=true
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
dht-listen-port=50101-50109
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
# BT校验相关, 默认:true
|
||||
#bt-hash-check-seed=true
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
bt-seed-unverified=true
|
||||
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
|
||||
bt-save-metadata=false
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
# To select files, use --select-file option. If it is not used,
|
||||
# all files are assumed to be selected. Please use this option with care
|
||||
# because it will actually remove files from your disk. Default: false
|
||||
bt-remove-unselected-file=true
|
||||
# Verify the peer using certificates specified
|
||||
# in --ca-certificate option. Default: true
|
||||
check-certificate=false
|
||||
# Exclude seed only downloads when counting concurrent active downloads (See -j option).
|
||||
# This means that if -j3 is given and this option is turned on and 3 downloads are active and one of those enters seed mode,
|
||||
# then it is excluded from active download count (thus it becomes 2),
|
||||
# and the next download waiting in queue gets started.
|
||||
# But be aware that seeding item is still recognized as active download in RPC method. Default: false
|
||||
bt-detach-seed-only=true
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Linux Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=trunc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -1,122 +0,0 @@
|
||||
## '#'开头为注释内容, 选项都有相应的注释说明, 根据需要修改 ##
|
||||
## 被注释的选项填写的是默认值, 建议在需要修改时再取消注释 ##
|
||||
## 添加了@和默认启用的选项都是系统需要调用的,请不要随意改动否则可能无法正常运行
|
||||
|
||||
## 文件保存相关 ##
|
||||
|
||||
# 文件的保存路径(可使用绝对路径或相对路径), 默认: 当前启动位置
|
||||
# 此项 OS X 无法使用 $HOME 及 ~/ 设置路径 建议使用 /users/用户名/downloads
|
||||
#@dir=$HOME/downloads
|
||||
# 启用磁盘缓存, 0为禁用缓存, 需1.16以上版本, 默认:16M
|
||||
#@disk-cache=32M
|
||||
# 文件预分配方式, 能有效降低磁盘碎片, 默认:prealloc
|
||||
# 预分配所需时间: none < falloc ? trunc < prealloc
|
||||
# falloc和trunc则需要文件系统和内核支持
|
||||
# NTFS建议使用falloc, EXT3/4建议trunc, MAC 下需要注释此项
|
||||
file-allocation=falloc
|
||||
# 断点续传
|
||||
#@continue=true
|
||||
|
||||
## 下载连接相关 ##
|
||||
|
||||
# 最大同时下载任务数, 运行时可修改, 默认:5
|
||||
#@max-concurrent-downloads=10
|
||||
# 同一服务器连接数, 添加时可指定, 默认:1
|
||||
#@max-connection-per-server=15
|
||||
# 最小文件分片大小, 添加时可指定, 取值范围1M -1024M, 默认:20M
|
||||
# 假定size=10M, 文件为20MiB 则使用两个来源下载; 文件为15MiB 则使用一个来源下载
|
||||
#@min-split-size=10M
|
||||
# 单个任务最大线程数, 添加时可指定, 默认:5
|
||||
#@split=15
|
||||
# 整体下载速度限制, 运行时可修改, 默认:0
|
||||
#@max-overall-download-limit=0
|
||||
# 单个任务下载速度限制, 默认:0
|
||||
#@max-download-limit=0
|
||||
# 整体上传速度限制, 运行时可修改, 默认:0
|
||||
max-overall-upload-limit=128K
|
||||
# 单个任务上传速度限制, 默认:0
|
||||
#@max-upload-limit=0
|
||||
# 禁用IPv6, 默认:false
|
||||
disable-ipv6=false
|
||||
#运行覆盖已存在文件
|
||||
#@allow-overwrite=true
|
||||
#自动重命名
|
||||
#@auto-file-renaming=true
|
||||
|
||||
## 进度保存相关 ##
|
||||
|
||||
# 从会话文件中读取下载任务
|
||||
#@input-file=/Users/Shared/aria2.session
|
||||
# 在Aria2退出时保存`错误/未完成`的下载任务到会话文件
|
||||
#@save-session=/Users/Shared/aria2.session
|
||||
# 定时保存会话, 0为退出时才保存, 需1.16.1以上版本, 默认:0
|
||||
save-session-interval=30
|
||||
|
||||
## RPC相关设置 ##
|
||||
|
||||
# 启用RPC, 默认:false
|
||||
enable-rpc=true
|
||||
# 允许所有来源, 默认:false
|
||||
rpc-allow-origin-all=true
|
||||
# 允许非外部访问, 默认:false
|
||||
rpc-listen-all=true
|
||||
# 事件轮询方式, 取值:[epoll, kqueue, port, poll, select], 不同系统默认值不同
|
||||
#event-poll=select
|
||||
# RPC监听端口, 端口被占用时可以修改, 默认:6800
|
||||
# 使用本客户端请勿修改此项
|
||||
# rpc-listen-port=6800
|
||||
# 设置的RPC授权令牌, v1.18.4新增功能, 取代 --rpc-user 和 --rpc-passwd 选项
|
||||
#rpc-secret=token
|
||||
|
||||
## BT/PT下载相关 ##
|
||||
|
||||
# 当下载的是一个种子(以.torrent结尾)时, 自动开始BT任务, 默认:true
|
||||
#follow-torrent=true
|
||||
# BT监听端口, 当端口被屏蔽时使用, 默认:6881-6999
|
||||
listen-port=50101-50109
|
||||
# 单个种子最大连接数, 默认:55
|
||||
#bt-max-peers=55
|
||||
# 打开DHT功能, PT需要禁用, 默认:true
|
||||
enable-dht=true
|
||||
# 打开IPv6 DHT功能, PT需要禁用
|
||||
enable-dht6=true
|
||||
# DHT网络监听端口, 默认:6881-6999
|
||||
dht-listen-port=50101-50109
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# 本地节点查找, PT需要禁用, 默认:false
|
||||
bt-enable-lpd=true
|
||||
# 种子交换, PT需要禁用, 默认:true
|
||||
enable-peer-exchange=true
|
||||
# 每个种子限速, 对少种的PT很有用, 默认:50K
|
||||
#bt-request-peer-speed-limit=50K
|
||||
# 客户端伪装, PT需要
|
||||
peer-id-prefix=-TR2770-
|
||||
user-agent=Transmission/2.94
|
||||
# 当种子的分享率达到这个数时, 自动停止做种, 0为一直做种, 默认:1.0
|
||||
seed-ratio=1.0
|
||||
# 强制保存会话, 即使任务已经完成, 默认:false
|
||||
# 较新的版本开启后会在任务完成后依然保留.aria2文件
|
||||
#force-save=false
|
||||
# BT校验相关, 默认:true
|
||||
#bt-hash-check-seed=true
|
||||
# 继续之前的BT任务时, 无需再次校验, 默认:false
|
||||
bt-seed-unverified=true
|
||||
# 保存磁力链接元数据为种子文件(.torrent文件), 默认:false
|
||||
bt-save-metadata=false
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
# To select files, use --select-file option. If it is not used,
|
||||
# all files are assumed to be selected. Please use this option with care
|
||||
# because it will actually remove files from your disk. Default: false
|
||||
bt-remove-unselected-file=true
|
||||
# Verify the peer using certificates specified
|
||||
# in --ca-certificate option. Default: true
|
||||
check-certificate=false
|
||||
# Exclude seed only downloads when counting concurrent active downloads (See -j option).
|
||||
# This means that if -j3 is given and this option is turned on and 3 downloads are active and one of those enters seed mode,
|
||||
# then it is excluded from active download count (thus it becomes 2),
|
||||
# and the next download waiting in queue gets started.
|
||||
# But be aware that seeding item is still recognized as active download in RPC method. Default: false
|
||||
bt-detach-seed-only=true
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Windows Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=none
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,91 @@
|
||||
###############################
|
||||
# Motrix Windows Aria2 config file
|
||||
#
|
||||
# @see https://aria2.github.io/manual/en/html/aria2c.html
|
||||
#
|
||||
###############################
|
||||
|
||||
|
||||
################ RPC ################
|
||||
# Enable JSON-RPC/XML-RPC server.
|
||||
enable-rpc=true
|
||||
# Add Access-Control-Allow-Origin header field with value * to the RPC response.
|
||||
rpc-allow-origin-all=true
|
||||
# Listen incoming JSON-RPC/XML-RPC requests on all network interfaces.
|
||||
rpc-listen-all=true
|
||||
|
||||
|
||||
################ File system ################
|
||||
# Save a control file(*.aria2) every SEC seconds.
|
||||
auto-save-interval=10
|
||||
# Enable disk cache.
|
||||
disk-cache=64M
|
||||
# Specify file allocation method.
|
||||
file-allocation=falloc
|
||||
# No file allocation is made for files whose size is smaller than SIZE
|
||||
no-file-allocation-limit=64M
|
||||
# Save error/unfinished downloads to a file specified by --save-session option every SEC seconds.
|
||||
save-session-interval=10
|
||||
|
||||
|
||||
################ Task ################
|
||||
# Exclude seed only downloads when counting concurrent active downloads
|
||||
bt-detach-seed-only=true
|
||||
# Verify the peer using certificates specified in --ca-certificate option.
|
||||
check-certificate=false
|
||||
# If aria2 receives "file not found" status from the remote HTTP/FTP servers NUM times
|
||||
# without getting a single byte, then force the download to fail.
|
||||
max-file-not-found=10
|
||||
# Set number of tries.
|
||||
max-tries=0
|
||||
# Set the seconds to wait between retries. When SEC > 0, aria2 will retry downloads when the HTTP server returns a 503 response.
|
||||
retry-wait=10
|
||||
# Set the connect timeout in seconds to establish connection to HTTP/FTP/proxy server. After the connection is established, this option makes no effect and --timeout option is used instead.
|
||||
connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
timeout=10
|
||||
# aria2 does not split less than 2*SIZE byte range.
|
||||
min-split-size=1M
|
||||
# Send Accept: deflate, gzip request header.
|
||||
http-accept-gzip=true
|
||||
# Retrieve timestamp of the remote file from the remote HTTP/FTP server and if it is available, apply it to the local file.
|
||||
remote-time=true
|
||||
# Set interval in seconds to output download progress summary. Setting 0 suppresses the output.
|
||||
summary-interval=0
|
||||
# Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename*.
|
||||
content-disposition-default-utf8=true
|
||||
|
||||
|
||||
################ BT Task ################
|
||||
# Enable Local Peer Discovery.
|
||||
bt-enable-lpd=true
|
||||
# Requires BitTorrent message payload encryption with arc4.
|
||||
# bt-force-encryption=true
|
||||
# If true is given, after hash check using --check-integrity option and file is complete, continue to seed file.
|
||||
bt-hash-check-seed=true
|
||||
# Specify the maximum number of peers per torrent.
|
||||
bt-max-peers=128
|
||||
# Try to download first and last pieces of each file first. This is useful for previewing files.
|
||||
bt-prioritize-piece=head
|
||||
# Removes the unselected files when download is completed in BitTorrent.
|
||||
bt-remove-unselected-file=true
|
||||
# Seed previously downloaded files without verifying piece hashes.
|
||||
bt-seed-unverified=false
|
||||
# Set the connect timeout in seconds to establish connection to tracker. After the connection is established, this option makes no effect and --bt-tracker-timeout option is used instead.
|
||||
bt-tracker-connect-timeout=10
|
||||
# Set timeout in seconds.
|
||||
bt-tracker-timeout=10
|
||||
# Set host and port as an entry point to IPv4 DHT network.
|
||||
dht-entry-point=dht.transmissionbt.com:6881
|
||||
# Set host and port as an entry point to IPv6 DHT network.
|
||||
dht-entry-point6=dht.transmissionbt.com:6881
|
||||
# Enable IPv4 DHT functionality. It also enables UDP tracker support.
|
||||
enable-dht=true
|
||||
# Enable IPv6 DHT functionality.
|
||||
enable-dht6=true
|
||||
# Enable Peer Exchange extension.
|
||||
enable-peer-exchange=true
|
||||
# Specify the string used during the bitorrent extended handshake for the peer's client version.
|
||||
peer-agent=Transmission/3.00
|
||||
# Specify the prefix of peer ID.
|
||||
peer-id-prefix=-TR3000-
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/renderer/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./src/shared/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "Motrix",
|
||||
"version": "1.4.1",
|
||||
"version": "1.8.19",
|
||||
"description": "A full-featured download manager",
|
||||
"homepage": "https://motrix.app",
|
||||
"author": {
|
||||
"name": "AGALWOOD",
|
||||
"name": "Dr_rOot",
|
||||
"email": "agalwood.net@gmail.com"
|
||||
},
|
||||
"copyright": "Copyright© AGALWOOD",
|
||||
"copyright": "Copyright© Dr_rOot",
|
||||
"license": "MIT",
|
||||
"main": "./dist/electron/main.js",
|
||||
"repository": {
|
||||
@@ -17,236 +17,98 @@
|
||||
"scripts": {
|
||||
"release": "npm run build --publish onTagOrDraft",
|
||||
"build": "node .electron-vue/build.js && electron-builder",
|
||||
"build:applesilicon": "node .electron-vue/build.js && electron-builder --arm64 --mac",
|
||||
"build:github": "node .electron-vue/build.js",
|
||||
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
|
||||
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
|
||||
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
|
||||
"dev": "node .electron-vue/dev-runner.js",
|
||||
"dev:renderer": "webpack-dev-server --hot --colors --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist",
|
||||
"dev:renderer": "webpack serve --node-env development --hot --color --config .electron-vue/webpack.renderer.config.js --port 9080 --content-base app/dist",
|
||||
"lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src",
|
||||
"lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src",
|
||||
"pack": "npm run pack:main && npm run pack:renderer",
|
||||
"pack:main": "cross-env NODE_ENV=production webpack --mode production --progress --colors --config .electron-vue/webpack.main.config.js",
|
||||
"pack:renderer": "cross-env NODE_ENV=production webpack --mode production --progress --colors --config .electron-vue/webpack.renderer.config.js",
|
||||
"pack:main": "webpack --node-env production --progress --color --config .electron-vue/webpack.main.config.js",
|
||||
"pack:renderer": "webpack --node-env production --progress --color --config .electron-vue/webpack.renderer.config.js",
|
||||
"postinstall": "electron-builder install-app-deps && npm run lint:fix"
|
||||
},
|
||||
"build": {
|
||||
"productName": "Motrix",
|
||||
"appId": "net.agalwood.Motrix",
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": "torrent",
|
||||
"mimeType": "application/x-bittorrent",
|
||||
"name": "Torrent",
|
||||
"role": "Viewer"
|
||||
}
|
||||
],
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
"name": "Motrix Protocol",
|
||||
"schemes": [
|
||||
"mo",
|
||||
"motrix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Magnet Protocol",
|
||||
"schemes": [
|
||||
"magnet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Thunder Protocol",
|
||||
"schemes": [
|
||||
"thunder"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dmg": {
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 380
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 230,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 230,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"dmg",
|
||||
"zip"
|
||||
],
|
||||
"type": "distribution",
|
||||
"darkModeSupport": true,
|
||||
"extraResources": {
|
||||
"from": "./extra/darwin/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"binaries": [
|
||||
"./release/mac/Motrix.app/Contents/Resources/engine/aria2c"
|
||||
],
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/win32/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true
|
||||
},
|
||||
"linux": {
|
||||
"category": "Network",
|
||||
"target": [
|
||||
"deb",
|
||||
"snap",
|
||||
"AppImage"
|
||||
],
|
||||
"extraResources": {
|
||||
"from": "./extra/linux/",
|
||||
"to": "./",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "https://dl.motrix.app/release/"
|
||||
},
|
||||
{
|
||||
"provider": "github"
|
||||
}
|
||||
]
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@panter/vue-i18next": "^0.15.1",
|
||||
"aria2": "^4.0.3",
|
||||
"axios": "^0.19.0",
|
||||
"blob-util": "^2.0.2",
|
||||
"clipboard-polyfill": "^2.8.1",
|
||||
"electron-debug": "^3.0.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^3.0.6",
|
||||
"electron-updater": "^4.0.12",
|
||||
"element-ui": "^2.9.1",
|
||||
"forever-monitor": "^1.7.1",
|
||||
"i18next": "^17.0.3",
|
||||
"lodash": "^4.17.11",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^6.1.2",
|
||||
"randomatic": "^3.1.1",
|
||||
"svg-innerhtml": "^1.1.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-router": "^3.0.6",
|
||||
"vuex": "^3.1.1",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.6.0",
|
||||
"@vue/cli-plugin-eslint": "^3.6.0",
|
||||
"@vue/cli-service": "^3.6.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"ajv": "^6.10.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-loader": "^7.1.5",
|
||||
"@babel/core": "^7.21.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.21.4",
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
"@babel/register": "^7.21.0",
|
||||
"@babel/runtime": "^7.21.5",
|
||||
"@bany/curl-to-json": "^1.2.7",
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"@electron/osx-sign": "^1.0.4",
|
||||
"@electron/remote": "^2.0.9",
|
||||
"@motrix/multispinner": "^0.2.4",
|
||||
"@motrix/nat-api": "^0.3.4",
|
||||
"@panter/vue-i18next": "^0.15.2",
|
||||
"@vue/eslint-config-standard": "^6.1.0",
|
||||
"ajv": "^8.12.0",
|
||||
"axios": "^1.4.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"babili-webpack-plugin": "^0.1.2",
|
||||
"cfonts": "^2.4.3",
|
||||
"chalk": "^2.4.2",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"cross-env": "^5.1.6",
|
||||
"css-loader": "^2.1.1",
|
||||
"del": "^4.1.1",
|
||||
"devtron": "^1.4.0",
|
||||
"electron": "^4.2.4",
|
||||
"electron-builder": "^20.43.0",
|
||||
"electron-devtools-installer": "^2.2.4",
|
||||
"electron-notarize": "^0.1.1",
|
||||
"electron-osx-sign": "^0.4.11",
|
||||
"electron-store": "^2.0.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"bittorrent-peerid": "^1.3.6",
|
||||
"blob-util": "^2.0.2",
|
||||
"cfonts": "^3.2.0",
|
||||
"chalk": "^4.1.2",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
||||
"del": "^6.1.1",
|
||||
"electron": "^22.3.9",
|
||||
"electron-builder": "^24.4.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-is": "^3.0.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^6.1.0",
|
||||
"element-ui": "^2.15.13",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-html": "^4.0.6",
|
||||
"eslint-plugin-import": "^2.17.3",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-promise": "^4.1.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^5.2.2",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "0.7.0",
|
||||
"multispinner": "^0.2.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"vue-html-loader": "^1.2.4",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack": "^4.33.0",
|
||||
"webpack-cli": "^3.3.4",
|
||||
"webpack-dev-server": "^3.7.1",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^4.2.1"
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.12.0",
|
||||
"eslint-webpack-plugin": "^4.0.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.1",
|
||||
"i18next": "^22.4.15",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-css-extract-plugin": "2.7.5",
|
||||
"node-loader": "^2.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"parse-torrent": "^9.1.5",
|
||||
"randomatic": "^3.1.1",
|
||||
"sass": "1.62.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.2",
|
||||
"terser-webpack-plugin": "^5.3.8",
|
||||
"vue": "^2.7.14",
|
||||
"vue-electron": "^1.0.6",
|
||||
"vue-loader": "^15.10.1",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-selectable": "^0.5.0",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vue-template-compiler": "^2.7.14",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-router-sync": "^5.0.0",
|
||||
"webpack": "^5.82.1",
|
||||
"webpack-cli": "^5.1.1",
|
||||
"webpack-dev-server": "^4.15.0",
|
||||
"webpack-hot-middleware": "^2.25.3",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"worker-loader": "^3.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 33 KiB |
@@ -10,22 +10,49 @@
|
||||
</script>
|
||||
<% } %>
|
||||
</head>
|
||||
|
||||
<style>
|
||||
.skeleton-aside {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
width: 78px;
|
||||
}
|
||||
.skeleton-subnav {
|
||||
background-color: #f4f5f7;
|
||||
width: 200px;
|
||||
}
|
||||
.skeleton-main {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.skeleton-aside {
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
.skeleton-subnav {
|
||||
background-color: #2D2D2D;
|
||||
}
|
||||
.skeleton-main {
|
||||
background-color: #343434;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="title-bar"></div>
|
||||
<section class="el-container" id="container">
|
||||
<aside class="el-aside aside" style="width: 78px;">
|
||||
<aside class="el-aside skeleton-aside hidden-sm-and-down">
|
||||
</aside>
|
||||
<aside class="el-aside subnav" style="width: 200px;">
|
||||
<aside class="el-aside skeleton-subnav hidden-xs-only">
|
||||
</aside>
|
||||
<section class="el-container main">
|
||||
<section class="el-container skeleton-main">
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<!-- Set `__static` path to static files in production -->
|
||||
<% if (!process.browser) { %>
|
||||
<% if (!htmlWebpackPlugin.options.isBrowser && !htmlWebpackPlugin.options.isDev) { %>
|
||||
<script>
|
||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { readFile, unlink } from 'node:fs'
|
||||
import { extname, basename } from 'node:path'
|
||||
import { app, shell, dialog, ipcMain } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { readFile } from 'fs'
|
||||
import { extname, basename } from 'path'
|
||||
import { isEmpty, isEqual } from 'lodash'
|
||||
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
AUTO_SYNC_TRACKER_INTERVAL,
|
||||
AUTO_CHECK_UPDATE_INTERVAL
|
||||
} from '@shared/constants'
|
||||
import { checkIsNeedRun } from '@shared/utils'
|
||||
import {
|
||||
convertTrackerDataToComma,
|
||||
fetchBtTrackerFromSource,
|
||||
reduceTrackerString
|
||||
} from '@shared/utils/tracker'
|
||||
import { showItemInFolder } from './utils'
|
||||
import logger from './core/Logger'
|
||||
import Context from './core/Context'
|
||||
import ConfigManager from './core/ConfigManager'
|
||||
import { setupLocaleManager } from '@/ui/Locale'
|
||||
import { setupLocaleManager } from './ui/Locale'
|
||||
import Engine from './core/Engine'
|
||||
import EngineClient from './core/EngineClient'
|
||||
import UPnPManager from './core/UPnPManager'
|
||||
import AutoLaunchManager from './core/AutoLaunchManager'
|
||||
import UpdateManager from './core/UpdateManager'
|
||||
import EnergyManager from './core/EnergyManager'
|
||||
@@ -16,8 +32,8 @@ import WindowManager from './ui/WindowManager'
|
||||
import MenuManager from './ui/MenuManager'
|
||||
import TouchBarManager from './ui/TouchBarManager'
|
||||
import TrayManager from './ui/TrayManager'
|
||||
import DockManager from './ui/DockManager'
|
||||
import ThemeManager from './ui/ThemeManager'
|
||||
import { AUTO_CHECK_UPDATE_INTERVAL } from '@shared/constants'
|
||||
|
||||
export default class Application extends EventEmitter {
|
||||
constructor () {
|
||||
@@ -27,32 +43,35 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
init () {
|
||||
this.configManager = new ConfigManager()
|
||||
this.initContext()
|
||||
|
||||
this.locale = this.configManager.getLocale()
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
this.initConfigManager()
|
||||
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
this.setupLogger()
|
||||
|
||||
this.initTouchBarManager()
|
||||
this.initLocaleManager()
|
||||
|
||||
this.setupApplicationMenu()
|
||||
|
||||
this.initWindowManager()
|
||||
|
||||
this.engine = new Engine({
|
||||
systemConfig: this.configManager.getSystemConfig(),
|
||||
userConfig: this.configManager.getUserConfig()
|
||||
})
|
||||
this.initUPnPManager()
|
||||
|
||||
this.startEngine()
|
||||
|
||||
this.trayManager = new TrayManager()
|
||||
this.initEngineClient()
|
||||
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
this.initTouchBarManager()
|
||||
|
||||
this.initThemeManager()
|
||||
|
||||
this.energyManager = new EnergyManager()
|
||||
this.initTrayManager()
|
||||
|
||||
this.initDockManager()
|
||||
|
||||
this.initAutoLaunchManager()
|
||||
|
||||
this.initEnergyManager()
|
||||
|
||||
this.initUpdaterManager()
|
||||
|
||||
@@ -60,11 +79,77 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.handleCommands()
|
||||
|
||||
this.handleEvents()
|
||||
|
||||
this.handleIpcMessages()
|
||||
|
||||
this.handleIpcInvokes()
|
||||
|
||||
this.emit('application:initialized')
|
||||
}
|
||||
|
||||
initContext () {
|
||||
this.context = new Context()
|
||||
}
|
||||
|
||||
initConfigManager () {
|
||||
this.configListeners = {}
|
||||
this.configManager = new ConfigManager()
|
||||
}
|
||||
|
||||
offConfigListeners () {
|
||||
try {
|
||||
Object.keys(this.configListeners).forEach((key) => {
|
||||
this.configListeners[key]()
|
||||
})
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] offConfigListeners===>', e)
|
||||
}
|
||||
this.configListeners = {}
|
||||
}
|
||||
|
||||
setupLogger () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'log-level'
|
||||
const logLevel = userConfig.get(key)
|
||||
logger.transports.file.level = logLevel
|
||||
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
logger.transports.file.level = newValue
|
||||
})
|
||||
}
|
||||
|
||||
initLocaleManager () {
|
||||
this.locale = this.configManager.getLocale()
|
||||
this.localeManager = setupLocaleManager(this.locale)
|
||||
this.i18n = this.localeManager.getI18n()
|
||||
}
|
||||
|
||||
setupApplicationMenu () {
|
||||
this.menuManager = new MenuManager()
|
||||
this.menuManager.setup(this.locale)
|
||||
}
|
||||
|
||||
adjustMenu () {
|
||||
if (is.mas()) {
|
||||
const visibleStates = {
|
||||
'app.check-for-updates': false,
|
||||
'task.new-bt-task': false
|
||||
}
|
||||
this.menuManager.updateMenuStates(visibleStates, null, null)
|
||||
this.trayManager.updateMenuStates(visibleStates, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
startEngine () {
|
||||
const self = this
|
||||
|
||||
try {
|
||||
this.engine = new Engine({
|
||||
systemConfig: this.configManager.getSystemConfig(),
|
||||
userConfig: this.configManager.getUserConfig()
|
||||
})
|
||||
this.engine.start()
|
||||
} catch (err) {
|
||||
const { message } = err
|
||||
@@ -72,14 +157,290 @@ export default class Application extends EventEmitter {
|
||||
type: 'error',
|
||||
title: this.i18n.t('app.system-error-title'),
|
||||
message: this.i18n.t('app.system-error-message', { message })
|
||||
}, () => {
|
||||
}).then(_ => {
|
||||
setTimeout(() => {
|
||||
app.quit()
|
||||
self.quit()
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async stopEngine () {
|
||||
logger.info('[Motrix] stopEngine===>')
|
||||
try {
|
||||
await this.engineClient.shutdown({ force: true })
|
||||
logger.info('[Motrix] stopEngine.setImmediate===>')
|
||||
setImmediate(() => {
|
||||
this.engine.stop()
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] shutdown engine fail: ', err.message)
|
||||
} finally {
|
||||
// no finally
|
||||
}
|
||||
}
|
||||
|
||||
initEngineClient () {
|
||||
const port = this.configManager.getSystemConfig('rpc-listen-port')
|
||||
const secret = this.configManager.getSystemConfig('rpc-secret')
|
||||
this.engineClient = new EngineClient({
|
||||
port,
|
||||
secret
|
||||
})
|
||||
}
|
||||
|
||||
initAutoLaunchManager () {
|
||||
this.autoLaunchManager = new AutoLaunchManager()
|
||||
}
|
||||
|
||||
initEnergyManager () {
|
||||
this.energyManager = new EnergyManager()
|
||||
}
|
||||
|
||||
initTrayManager () {
|
||||
this.trayManager = new TrayManager({
|
||||
theme: this.configManager.getUserConfig('tray-theme'),
|
||||
systemTheme: this.themeManager.getSystemTheme(),
|
||||
speedometer: this.configManager.getUserConfig('tray-speedometer'),
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
|
||||
this.watchTraySpeedometerEnabledChange()
|
||||
|
||||
this.trayManager.on('mouse-down', ({ focused }) => {
|
||||
this.sendCommandToAll('application:update-tray-focused', { focused })
|
||||
})
|
||||
|
||||
this.trayManager.on('mouse-up', ({ focused }) => {
|
||||
this.sendCommandToAll('application:update-tray-focused', { focused })
|
||||
})
|
||||
|
||||
this.trayManager.on('drop-files', (files = []) => {
|
||||
this.handleFile(files[0])
|
||||
})
|
||||
|
||||
this.trayManager.on('drop-text', (text) => {
|
||||
this.handleProtocol(text)
|
||||
})
|
||||
}
|
||||
|
||||
watchTraySpeedometerEnabledChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'tray-speedometer'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
this.trayManager.handleSpeedometerEnableChange(newValue)
|
||||
})
|
||||
}
|
||||
|
||||
initDockManager () {
|
||||
this.dockManager = new DockManager({
|
||||
runMode: this.configManager.getUserConfig('run-mode')
|
||||
})
|
||||
}
|
||||
|
||||
watchOpenAtLoginChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'open-at-login'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (newValue) {
|
||||
this.autoLaunchManager.enable()
|
||||
} else {
|
||||
this.autoLaunchManager.disable()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watchProtocolsChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'protocols'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
|
||||
if (!newValue || isEqual(newValue, oldValue)) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('[Motrix] setup protocols client:', newValue)
|
||||
this.protocolManager.setup(newValue)
|
||||
})
|
||||
}
|
||||
|
||||
watchRunModeChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'run-mode'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
this.trayManager.handleRunModeChange(newValue)
|
||||
|
||||
if (newValue !== APP_RUN_MODE.TRAY) {
|
||||
this.dockManager.show()
|
||||
} else {
|
||||
this.dockManager.hide()
|
||||
// Hiding the dock icon will trigger the entire app to hide.
|
||||
this.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watchShowProgressBarChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'show-progress-bar'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info(`[Motrix] detected ${key} value change event:`, newValue, oldValue)
|
||||
|
||||
if (newValue) {
|
||||
this.bindProgressChange()
|
||||
} else {
|
||||
this.unbindProgressChange()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
initUPnPManager () {
|
||||
this.upnp = new UPnPManager()
|
||||
|
||||
this.watchUPnPEnabledChange()
|
||||
|
||||
this.watchUPnPPortsChange()
|
||||
|
||||
const enabled = this.configManager.getUserConfig('enable-upnp')
|
||||
if (!enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.startUPnPMapping()
|
||||
}
|
||||
|
||||
async startUPnPMapping () {
|
||||
const btPort = this.configManager.getSystemConfig('listen-port')
|
||||
const dhtPort = this.configManager.getSystemConfig('dht-listen-port')
|
||||
|
||||
const promises = [
|
||||
this.upnp.map(btPort),
|
||||
this.upnp.map(dhtPort)
|
||||
]
|
||||
try {
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] start UPnP mapping fail', e.message)
|
||||
}
|
||||
}
|
||||
|
||||
async stopUPnPMapping () {
|
||||
const btPort = this.configManager.getSystemConfig('listen-port')
|
||||
const dhtPort = this.configManager.getSystemConfig('dht-listen-port')
|
||||
|
||||
const promises = [
|
||||
this.upnp.unmap(btPort),
|
||||
this.upnp.unmap(dhtPort)
|
||||
]
|
||||
try {
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.warn('[Motrix] stop UPnP mapping fail', e)
|
||||
}
|
||||
}
|
||||
|
||||
watchUPnPPortsChange () {
|
||||
const { systemConfig } = this.configManager
|
||||
const watchKeys = ['listen-port', 'dht-listen-port']
|
||||
|
||||
watchKeys.forEach((key) => {
|
||||
this.configListeners[key] = systemConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info('[Motrix] detected port change event:', key, newValue, oldValue)
|
||||
const enable = this.configManager.getUserConfig('enable-upnp')
|
||||
if (!enable) {
|
||||
return
|
||||
}
|
||||
|
||||
const promises = [
|
||||
this.upnp.unmap(oldValue),
|
||||
this.upnp.map(newValue)
|
||||
]
|
||||
try {
|
||||
await Promise.allSettled(promises)
|
||||
} catch (e) {
|
||||
logger.info('[Motrix] change UPnP port mapping failed:', e)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
watchUPnPEnabledChange () {
|
||||
const { userConfig } = this.configManager
|
||||
const key = 'enable-upnp'
|
||||
this.configListeners[key] = userConfig.onDidChange(key, async (newValue, oldValue) => {
|
||||
logger.info('[Motrix] detected enable-upnp value change event:', newValue, oldValue)
|
||||
if (newValue) {
|
||||
this.startUPnPMapping()
|
||||
} else {
|
||||
await this.stopUPnPMapping()
|
||||
this.upnp.closeClient()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async shutdownUPnPManager () {
|
||||
const enable = this.configManager.getUserConfig('enable-upnp')
|
||||
if (enable) {
|
||||
await this.stopUPnPMapping()
|
||||
}
|
||||
|
||||
this.upnp.closeClient()
|
||||
}
|
||||
|
||||
autoSyncTracker () {
|
||||
const enable = this.configManager.getUserConfig('auto-sync-tracker')
|
||||
const lastTime = this.configManager.getUserConfig('last-sync-tracker-time')
|
||||
const result = checkIsNeedRun(enable, lastTime, AUTO_SYNC_TRACKER_INTERVAL)
|
||||
logger.info('[Motrix] auto sync tracker checkIsNeedRun:', result)
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
|
||||
const source = this.configManager.getUserConfig('tracker-source')
|
||||
if (isEmpty(source)) {
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
fetchBtTrackerFromSource(source).then((data) => {
|
||||
logger.warn('[Motrix] auto sync tracker data:', data)
|
||||
if (!data || data.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let tracker = convertTrackerDataToComma(data)
|
||||
tracker = reduceTrackerString(tracker)
|
||||
this.savePreference({
|
||||
system: {
|
||||
'bt-tracker': tracker
|
||||
},
|
||||
user: {
|
||||
'last-sync-tracker-time': Date.now()
|
||||
}
|
||||
})
|
||||
}).catch((err) => {
|
||||
logger.warn('[Motrix] auto sync tracker failed:', err.message)
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
|
||||
autoResumeTask () {
|
||||
const enabled = this.configManager.getUserConfig('resume-all-when-app-launched')
|
||||
if (!enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
this.engineClient.call('unpauseAll')
|
||||
}
|
||||
|
||||
initWindowManager () {
|
||||
this.windowManager = new WindowManager({
|
||||
userConfig: this.configManager.getUserConfig()
|
||||
@@ -88,12 +449,25 @@ export default class Application extends EventEmitter {
|
||||
this.windowManager.on('window-resized', (data) => {
|
||||
this.storeWindowState(data)
|
||||
})
|
||||
|
||||
this.windowManager.on('window-moved', (data) => {
|
||||
this.storeWindowState(data)
|
||||
})
|
||||
|
||||
this.windowManager.on('window-closed', (data) => {
|
||||
this.storeWindowState(data)
|
||||
})
|
||||
|
||||
this.windowManager.on('enter-full-screen', (window) => {
|
||||
this.dockManager.show()
|
||||
})
|
||||
|
||||
this.windowManager.on('leave-full-screen', (window) => {
|
||||
const mode = this.configManager.getUserConfig('run-mode')
|
||||
if (mode === APP_RUN_MODE.TRAY) {
|
||||
this.dockManager.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
storeWindowState (data = {}) {
|
||||
@@ -112,23 +486,26 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
start (page, options = {}) {
|
||||
this.showPage(page, options)
|
||||
}
|
||||
const win = this.showPage(page, options)
|
||||
|
||||
showPage (page, options = {}) {
|
||||
const { openedAtLogin } = options
|
||||
const win = this.windowManager.openWindow(page, {
|
||||
hidden: openedAtLogin
|
||||
})
|
||||
win.once('ready-to-show', () => {
|
||||
this.isReady = true
|
||||
this.emit('ready')
|
||||
})
|
||||
|
||||
if (is.macOS()) {
|
||||
this.touchBarManager.setup(page, win)
|
||||
}
|
||||
}
|
||||
|
||||
showPage (page, options = {}) {
|
||||
const { openedAtLogin } = options
|
||||
const autoHideWindow = this.configManager.getUserConfig('auto-hide-window')
|
||||
return this.windowManager.openWindow(page, {
|
||||
hidden: openedAtLogin || autoHideWindow
|
||||
})
|
||||
}
|
||||
|
||||
show (page = 'index') {
|
||||
this.windowManager.showWindow(page)
|
||||
}
|
||||
@@ -150,9 +527,27 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
stop () {
|
||||
this.engine.stop()
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
this.trayManager.destroy()
|
||||
try {
|
||||
const promises = [
|
||||
this.stopEngine(),
|
||||
this.shutdownUPnPManager(),
|
||||
this.energyManager.stopPowerSaveBlocker(),
|
||||
this.trayManager.destroy()
|
||||
]
|
||||
|
||||
return promises
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] stop error: ', err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async stopAllSettled () {
|
||||
await Promise.allSettled(this.stop())
|
||||
}
|
||||
|
||||
async quit () {
|
||||
await this.stopAllSettled()
|
||||
app.exit()
|
||||
}
|
||||
|
||||
sendCommand (command, ...args) {
|
||||
@@ -180,9 +575,9 @@ export default class Application extends EventEmitter {
|
||||
|
||||
initThemeManager () {
|
||||
this.themeManager = new ThemeManager()
|
||||
this.themeManager.on('system-theme-changed', (theme) => {
|
||||
this.trayManager.changeIconTheme(theme)
|
||||
this.sendCommandToAll('application:system-theme', theme)
|
||||
this.themeManager.on('system-theme-change', (theme) => {
|
||||
this.trayManager.handleSystemThemeChange(theme)
|
||||
this.sendCommandToAll('application:update-system-theme', { theme })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -190,13 +585,11 @@ export default class Application extends EventEmitter {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.touchBarManager = new TouchBarManager()
|
||||
}
|
||||
|
||||
initProtocolManager () {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
const protocols = this.configManager.getUserConfig('protocols', {})
|
||||
this.protocolManager = new ProtocolManager({
|
||||
protocols
|
||||
@@ -204,10 +597,6 @@ export default class Application extends EventEmitter {
|
||||
}
|
||||
|
||||
handleProtocol (url) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.show()
|
||||
|
||||
this.protocolManager.handle(url)
|
||||
@@ -224,15 +613,17 @@ export default class Application extends EventEmitter {
|
||||
|
||||
this.show()
|
||||
|
||||
const fileName = basename(filePath)
|
||||
const name = basename(filePath)
|
||||
readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] read file error: ${filePath}`, err.message)
|
||||
return
|
||||
}
|
||||
const file = Buffer.from(data).toString('base64')
|
||||
const args = [fileName, file]
|
||||
this.sendCommandToAll('application:new-bt-task-with-file', ...args)
|
||||
const dataURL = Buffer.from(data).toString('base64')
|
||||
this.sendCommandToAll('application:new-bt-task-with-file', {
|
||||
name,
|
||||
dataURL
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -240,27 +631,20 @@ export default class Application extends EventEmitter {
|
||||
if (is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
const enabled = this.configManager.getUserConfig('auto-check-update')
|
||||
const lastTime = this.configManager.getUserConfig('last-check-update-time')
|
||||
this.updateManager = new UpdateManager({
|
||||
autoCheck: this.isNeedAutoCheck(),
|
||||
setCheckTime: this.configManager
|
||||
autoCheck: checkIsNeedRun(enabled, lastTime, AUTO_CHECK_UPDATE_INTERVAL)
|
||||
})
|
||||
this.handleUpdaterEvents()
|
||||
}
|
||||
|
||||
isNeedAutoCheck () {
|
||||
const enable = this.configManager.getUserConfig('auto-check-update')
|
||||
if (!enable) {
|
||||
return false
|
||||
}
|
||||
|
||||
const lastCheck = this.configManager.getUserConfig('last-check-update-time')
|
||||
return (Date.now() - lastCheck > AUTO_CHECK_UPDATE_INTERVAL)
|
||||
}
|
||||
|
||||
handleUpdaterEvents () {
|
||||
this.updateManager.on('checking', (event) => {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', false)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', false)
|
||||
this.configManager.setUserConfig('last-check-update-time', Date.now())
|
||||
})
|
||||
|
||||
this.updateManager.on('download-progress', (event) => {
|
||||
@@ -277,11 +661,19 @@ export default class Application extends EventEmitter {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
const win = this.windowManager.getWindow('index')
|
||||
win.setProgressBar(0)
|
||||
win.setProgressBar(1)
|
||||
})
|
||||
|
||||
this.updateManager.on('will-updated', (event) => {
|
||||
this.updateManager.on('update-cancelled', (event) => {
|
||||
this.menuManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
this.trayManager.updateMenuItemEnabledState('app.check-for-updates', true)
|
||||
const win = this.windowManager.getWindow('index')
|
||||
win.setProgressBar(-1)
|
||||
})
|
||||
|
||||
this.updateManager.on('will-updated', async (event) => {
|
||||
this.windowManager.setWillQuit(true)
|
||||
await this.stopAllSettled()
|
||||
})
|
||||
|
||||
this.updateManager.on('update-error', (event) => {
|
||||
@@ -290,51 +682,69 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
relaunch (page = 'index') {
|
||||
this.stop()
|
||||
async relaunch () {
|
||||
await this.stopAllSettled()
|
||||
app.relaunch()
|
||||
app.exit()
|
||||
// this.closePage(page)
|
||||
// if (page === 'index') {
|
||||
// this.engine.restart()
|
||||
// }
|
||||
// setTimeout(() => {
|
||||
// this.showPage(page)
|
||||
// }, 500)
|
||||
}
|
||||
|
||||
async resetSession () {
|
||||
await this.stopEngine()
|
||||
|
||||
app.clearRecentDocuments()
|
||||
|
||||
const sessionPath = this.context.get('session-path')
|
||||
setTimeout(() => {
|
||||
unlink(sessionPath, (err) => {
|
||||
logger.info('[Motrix] Removed the download seesion file:', err)
|
||||
})
|
||||
|
||||
this.engine.start()
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
savePreference (config = {}) {
|
||||
logger.info('[Motrix] save preference:', config)
|
||||
const { system, user } = config
|
||||
if (!isEmpty(system)) {
|
||||
console.info('[Motrix] main save system config: ', system)
|
||||
this.configManager.setSystemConfig(system)
|
||||
this.engineClient.changeGlobalOption(system)
|
||||
}
|
||||
|
||||
if (!isEmpty(user)) {
|
||||
console.info('[Motrix] main save user config: ', user)
|
||||
this.configManager.setUserConfig(user)
|
||||
}
|
||||
}
|
||||
|
||||
handleCommands () {
|
||||
this.on('application:save-preference', this.savePreference)
|
||||
|
||||
this.on('application:update-tray', (tray) => {
|
||||
this.trayManager.updateTrayByImage(tray)
|
||||
})
|
||||
|
||||
this.on('application:relaunch', () => {
|
||||
this.relaunch()
|
||||
})
|
||||
|
||||
this.on('application:exit', () => {
|
||||
this.stop()
|
||||
app.exit()
|
||||
this.on('application:quit', () => {
|
||||
this.quit()
|
||||
})
|
||||
|
||||
this.on('application:open-at-login', (openAtLogin) => {
|
||||
console.log('application:open-at-login===>', openAtLogin)
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (openAtLogin) {
|
||||
this.autoLaunchManager.enable()
|
||||
} else {
|
||||
this.autoLaunchManager.disable()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('application:show', (page) => {
|
||||
this.on('application:show', ({ page }) => {
|
||||
this.show(page)
|
||||
})
|
||||
|
||||
this.on('application:hide', (page) => {
|
||||
this.on('application:hide', ({ page }) => {
|
||||
this.hide(page)
|
||||
})
|
||||
|
||||
this.on('application:reset-session', () => this.resetSession())
|
||||
|
||||
this.on('application:reset', () => {
|
||||
this.offConfigListeners()
|
||||
this.configManager.reset()
|
||||
this.relaunch()
|
||||
})
|
||||
@@ -344,19 +754,41 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
this.on('application:change-theme', (theme) => {
|
||||
this.themeManager.updateAppAppearance(theme)
|
||||
this.sendCommandToAll('application:theme', theme)
|
||||
this.themeManager.updateSystemTheme(theme)
|
||||
this.sendCommandToAll('application:update-theme', { theme })
|
||||
})
|
||||
|
||||
this.on('application:change-locale', (locale) => {
|
||||
logger.info('[Motrix] application:change-locale===>', locale)
|
||||
this.localeManager.changeLanguageByLocale(locale)
|
||||
.then(() => {
|
||||
this.menuManager.setup(locale)
|
||||
this.trayManager.setup(locale)
|
||||
this.menuManager.handleLocaleChange(locale)
|
||||
this.trayManager.handleLocaleChange(locale)
|
||||
})
|
||||
})
|
||||
|
||||
this.on('application:toggle-dock', (visible) => {
|
||||
if (visible) {
|
||||
this.dockManager.show()
|
||||
} else {
|
||||
this.dockManager.hide()
|
||||
// Hiding the dock icon will trigger the entire app to hide.
|
||||
this.show()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('application:auto-hide-window', (hide) => {
|
||||
if (hide) {
|
||||
this.windowManager.handleWindowBlur()
|
||||
} else {
|
||||
this.windowManager.unbindWindowBlur()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('application:change-menu-states', (visibleStates, enabledStates, checkedStates) => {
|
||||
this.menuManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
this.trayManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
})
|
||||
|
||||
this.on('application:open-file', (event) => {
|
||||
dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
@@ -366,8 +798,8 @@ export default class Application extends EventEmitter {
|
||||
extensions: ['torrent']
|
||||
}
|
||||
]
|
||||
}, (filePaths) => {
|
||||
if (!filePaths || filePaths.length === 0) {
|
||||
}).then(({ canceled, filePaths }) => {
|
||||
if (canceled || filePaths.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -381,47 +813,157 @@ export default class Application extends EventEmitter {
|
||||
})
|
||||
|
||||
this.on('application:setup-protocols-client', (protocols) => {
|
||||
if (is.dev() || is.mas()) {
|
||||
if (is.dev() || is.mas() || !protocols) {
|
||||
return
|
||||
}
|
||||
console.log('this.protocolManager', protocols)
|
||||
logger.info('[Motrix] setup protocols client:', protocols)
|
||||
this.protocolManager.setup(protocols)
|
||||
})
|
||||
|
||||
this.on('application:open-external', (url) => {
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('application:reveal-in-folder', (data) => {
|
||||
const { gid, path } = data
|
||||
logger.info('[Motrix] application:reveal-in-folder===>', path)
|
||||
if (path) {
|
||||
showItemInFolder(path)
|
||||
}
|
||||
if (gid) {
|
||||
this.sendCommandToAll('application:show-task-detail', { gid })
|
||||
}
|
||||
})
|
||||
|
||||
this.on('help:official-website', () => {
|
||||
const url = 'https://motrix.app/'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:manual', () => {
|
||||
const url = 'https://motrix.app/manual'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:release-notes', () => {
|
||||
const url = 'https://motrix.app/release'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
|
||||
this.on('help:report-problem', () => {
|
||||
const url = 'https://motrix.app/report'
|
||||
shell.openExternal(url)
|
||||
this.openExternal(url)
|
||||
})
|
||||
}
|
||||
|
||||
openExternal (url) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
|
||||
shell.openExternal(url)
|
||||
}
|
||||
|
||||
handleConfigChange (configName) {
|
||||
this.sendCommandToAll('application:update-preference-config', { configName })
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
this.once('application:initialized', () => {
|
||||
this.autoSyncTracker()
|
||||
|
||||
this.autoResumeTask()
|
||||
|
||||
this.adjustMenu()
|
||||
})
|
||||
|
||||
this.configManager.userConfig.onDidAnyChange(() => this.handleConfigChange('user'))
|
||||
this.configManager.systemConfig.onDidAnyChange(() => this.handleConfigChange('system'))
|
||||
|
||||
this.watchOpenAtLoginChange()
|
||||
this.watchProtocolsChange()
|
||||
this.watchRunModeChange()
|
||||
this.watchShowProgressBarChange()
|
||||
|
||||
this.on('download-status-change', (downloading) => {
|
||||
this.trayManager.handleDownloadStatusChange(downloading)
|
||||
if (downloading) {
|
||||
this.energyManager.startPowerSaveBlocker()
|
||||
} else {
|
||||
this.energyManager.stopPowerSaveBlocker()
|
||||
}
|
||||
})
|
||||
|
||||
this.on('speed-change', (speed) => {
|
||||
this.dockManager.handleSpeedChange(speed)
|
||||
this.trayManager.handleSpeedChange(speed)
|
||||
})
|
||||
|
||||
this.on('task-download-complete', (task, path) => {
|
||||
this.dockManager.openDock(path)
|
||||
|
||||
if (is.linux()) {
|
||||
return
|
||||
}
|
||||
app.addRecentDocument(path)
|
||||
})
|
||||
|
||||
if (this.configManager.userConfig.get('show-progress-bar')) {
|
||||
this.bindProgressChange()
|
||||
}
|
||||
}
|
||||
|
||||
handleProgressChange (progress) {
|
||||
if (this.updateManager.isChecking) {
|
||||
return
|
||||
}
|
||||
if (!is.windows() && progress === 2) {
|
||||
progress = 0
|
||||
}
|
||||
this.windowManager.getWindow('index').setProgressBar(progress)
|
||||
}
|
||||
|
||||
bindProgressChange () {
|
||||
if (this.listeners('progress-change').length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.on('progress-change', this.handleProgressChange)
|
||||
}
|
||||
|
||||
unbindProgressChange () {
|
||||
if (this.listeners('progress-change').length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.off('progress-change', this.handleProgressChange)
|
||||
this.windowManager.getWindow('index').setProgressBar(-1)
|
||||
}
|
||||
|
||||
handleIpcMessages () {
|
||||
ipcMain.on('command', (event, command, ...args) => {
|
||||
logger.log('receive command', command, ...args)
|
||||
logger.log('[Motrix] ipc receive command', command, ...args)
|
||||
this.emit(command, ...args)
|
||||
})
|
||||
|
||||
ipcMain.on('update-menu-states', (event, visibleStates, enabledStates, checkedStates) => {
|
||||
this.menuManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
this.trayManager.updateMenuStates(visibleStates, enabledStates, checkedStates)
|
||||
ipcMain.on('event', (event, eventName, ...args) => {
|
||||
logger.log('[Motrix] ipc receive event', eventName, ...args)
|
||||
this.emit(eventName, ...args)
|
||||
})
|
||||
}
|
||||
|
||||
ipcMain.on('download-status-change', (event, status) => {
|
||||
this.trayManager.updateStatus(status)
|
||||
handleIpcInvokes () {
|
||||
ipcMain.handle('get-app-config', async () => {
|
||||
const systemConfig = this.configManager.getSystemConfig()
|
||||
const userConfig = this.configManager.getUserConfig()
|
||||
const context = this.context.get()
|
||||
|
||||
const result = {
|
||||
...systemConfig,
|
||||
...userConfig,
|
||||
...context
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
parseArgvAsUrl,
|
||||
parseArgvAsFile
|
||||
} from './utils'
|
||||
|
||||
const EMPTY_STRING = ''
|
||||
import { EMPTY_STRING } from '@shared/constants'
|
||||
|
||||
export default class Launcher extends EventEmitter {
|
||||
constructor () {
|
||||
@@ -27,7 +26,7 @@ export default class Launcher extends EventEmitter {
|
||||
makeSingleInstance (callback) {
|
||||
// Mac App Store Sandboxed App not support requestSingleInstanceLock
|
||||
if (is.mas()) {
|
||||
callback()
|
||||
callback && callback()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,14 +36,13 @@ export default class Launcher extends EventEmitter {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, argv, workingDirectory) => {
|
||||
logger.warn('second-instance====>', argv, workingDirectory)
|
||||
global.application.showPage('index')
|
||||
if (!is.macOS() && argv.length > 1) {
|
||||
this.handleAppLaunchArgv(argv)
|
||||
}
|
||||
})
|
||||
|
||||
callback()
|
||||
callback && callback()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +57,13 @@ export default class Launcher extends EventEmitter {
|
||||
this.handleAppLaunchArgv(process.argv)
|
||||
}
|
||||
|
||||
logger.warn('openedAtLogin===>', this.openedAtLogin)
|
||||
logger.info('[Motrix] openedAtLogin:', this.openedAtLogin)
|
||||
|
||||
this.handleAppEvents()
|
||||
}
|
||||
|
||||
handleAppEvents () {
|
||||
this.handleRendererRemote()
|
||||
this.handleOpenUrl()
|
||||
this.handleOpenFile()
|
||||
|
||||
@@ -72,6 +71,12 @@ export default class Launcher extends EventEmitter {
|
||||
this.handleAppWillQuit()
|
||||
}
|
||||
|
||||
handleRendererRemote () {
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
require('@electron/remote/main').enable(window.webContents)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* handleOpenUrl
|
||||
* Event 'open-url' macOS only
|
||||
@@ -113,12 +118,12 @@ export default class Launcher extends EventEmitter {
|
||||
* @param {array} argv
|
||||
*/
|
||||
handleAppLaunchArgv (argv) {
|
||||
logger.info('handleAppLaunchArgv===>', argv)
|
||||
logger.info('[Motrix] handleAppLaunchArgv:', argv)
|
||||
|
||||
// args: array, extra: map
|
||||
const { args, extra } = splitArgv(argv)
|
||||
logger.info('splitArgv.args===>', args)
|
||||
logger.info('splitArgv.extra===>', extra)
|
||||
logger.info('[Motrix] split argv args:', args)
|
||||
logger.info('[Motrix] split argv extra:', extra)
|
||||
if (extra['--opened-at-login'] === '1') {
|
||||
this.openedAtLogin = true
|
||||
}
|
||||
@@ -178,6 +183,7 @@ export default class Launcher extends EventEmitter {
|
||||
app.on('will-quit', () => {
|
||||
logger.info('[Motrix] will-quit')
|
||||
if (global.application) {
|
||||
logger.info('[Motrix] will-quit.application.stop')
|
||||
global.application.stop()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
export default {
|
||||
'darwin': 'aria2c',
|
||||
'win32': 'aria2c.exe',
|
||||
'linux': 'aria2c'
|
||||
export const engineBinMap = {
|
||||
darwin: 'aria2c',
|
||||
win32: 'aria2c.exe',
|
||||
linux: 'aria2c'
|
||||
}
|
||||
|
||||
export const engineArchMap = {
|
||||
darwin: {
|
||||
x64: 'x64',
|
||||
arm64: 'arm64'
|
||||
},
|
||||
win32: {
|
||||
ia32: 'ia32',
|
||||
x64: 'x64',
|
||||
arm64: 'x64'
|
||||
},
|
||||
linux: {
|
||||
x64: 'x64',
|
||||
arm: 'armv7l',
|
||||
arm64: 'arm64'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ export default {
|
||||
title: 'Motrix',
|
||||
width: 1024,
|
||||
height: 768,
|
||||
minWidth: 840,
|
||||
minWidth: 478,
|
||||
minHeight: 420,
|
||||
// backgroundColor: '#FFFFFF',
|
||||
transparent: !is.windows()
|
||||
transparent: is.macOS()
|
||||
},
|
||||
bindCloseToHide: true,
|
||||
url: is.dev() ? `http://localhost:9080` : `file://${__dirname}/index.html`
|
||||
openDevTools: is.dev(),
|
||||
url: is.dev() ? 'http://localhost:9080' : require('path').join('file://', __dirname, '/index.html')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/* eslint quote-props: ["error", "always"] */
|
||||
export default {
|
||||
'task-list': 'application:task-list',
|
||||
'new-task': 'application:new-task',
|
||||
'new-bt-task': 'application:new-bt-task',
|
||||
'pause-all-task': 'application:pause-all-task',
|
||||
'resume-all-task': 'application:resume-all-task',
|
||||
'reveal-in-folder': 'application:reveal-in-folder',
|
||||
'preferences': 'application:preferences',
|
||||
'about': 'application:about'
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
export default [
|
||||
'udp://62.138.0.158:6969/announce',
|
||||
'udp://93.158.213.92:1337/announce',
|
||||
'udp://185.225.17.100:1337/announce',
|
||||
'udp://151.80.120.112:2710/announce',
|
||||
'udp://151.80.120.114:2710/announce',
|
||||
'udp://185.19.107.254:80/announce',
|
||||
'udp://208.83.20.20:6969/announce',
|
||||
'udp://5.206.27.172:6969/announce',
|
||||
'udp://176.31.241.153:80/announce',
|
||||
'udp://37.235.174.46:2710/announce',
|
||||
'udp://95.211.168.204:2710/announce',
|
||||
'udp://159.100.245.181:6969/announce',
|
||||
'http://51.68.122.172:80/announce',
|
||||
'udp://89.234.156.205:451/announce',
|
||||
'udp://184.105.151.164:6969/announce',
|
||||
'udp://51.15.40.114:80/announce',
|
||||
'http://82.209.230.66:80/announce',
|
||||
'udp://185.83.215.123:6969/announce',
|
||||
'udp://195.154.52.99:80/announce',
|
||||
'http://51.38.230.101:80/announce',
|
||||
'udp://tracker.coppersurfer.tk:6969/announce',
|
||||
'udp://tracker.opentrackr.org:1337/announce',
|
||||
'udp://tracker.internetwarriors.net:1337/announce',
|
||||
'udp://9.rarbg.to:2710/announce',
|
||||
'udp://9.rarbg.me:2710/announce',
|
||||
'udp://tracker.openbittorrent.com:80/announce',
|
||||
'udp://exodus.desync.com:6969/announce',
|
||||
'udp://tracker.tiny-vps.com:6969/announce',
|
||||
'udp://thetracker.org:80/announce',
|
||||
'udp://retracker.lanta-net.ru:2710/announce',
|
||||
'udp://bt.xxx-tracker.com:2710/announce',
|
||||
'udp://tracker.cyberia.is:6969/announce',
|
||||
'http://open.acgnxtracker.com:80/announce',
|
||||
'udp://tracker.torrent.eu.org:451/announce',
|
||||
'udp://explodie.org:6969/announce',
|
||||
'udp://ipv4.tracker.harry.lu:80/announce',
|
||||
'http://retracker.mgts.by:80/announce',
|
||||
'udp://tracker.uw0.xyz:6969/announce',
|
||||
'udp://open.stealth.si:80/announce',
|
||||
'http://t.nyaatracker.com:80/announce'
|
||||
]
|
||||
@@ -1,19 +1,18 @@
|
||||
import { app } from 'electron'
|
||||
|
||||
import { LOGIN_SETTING_OPTIONS } from '@shared/constants'
|
||||
|
||||
export default class AutoLaunchManager {
|
||||
enable () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enabled = app.getLoginItemSettings().openAtLogin
|
||||
const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin
|
||||
if (enabled) {
|
||||
resolve()
|
||||
}
|
||||
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
// For Windows
|
||||
args: [
|
||||
'--opened-at-login=1'
|
||||
]
|
||||
...LOGIN_SETTING_OPTIONS,
|
||||
openAtLogin: true
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
@@ -28,7 +27,7 @@ export default class AutoLaunchManager {
|
||||
|
||||
isEnabled () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enabled = app.getLoginItemSettings().openAtLogin
|
||||
const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin
|
||||
resolve(enabled)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import Store from 'electron-store'
|
||||
import tracker from '../configs/tracker'
|
||||
|
||||
import {
|
||||
getConfigBasePath,
|
||||
getDhtPath,
|
||||
getLogPath,
|
||||
getSessionPath,
|
||||
getMaxConnectionPerServer,
|
||||
getUserDownloadsPath
|
||||
} from '../utils/index'
|
||||
import {
|
||||
APP_RUN_MODE,
|
||||
APP_THEME,
|
||||
EMPTY_STRING,
|
||||
ENGINE_RPC_PORT,
|
||||
IP_VERSION,
|
||||
LOGIN_SETTING_OPTIONS,
|
||||
NGOSANG_TRACKERS_BEST_IP_URL_CDN,
|
||||
NGOSANG_TRACKERS_BEST_URL_CDN
|
||||
} from '@shared/constants'
|
||||
import { CHROME_UA } from '@shared/ua'
|
||||
import { separateConfig } from '@shared/utils'
|
||||
import { reduceTrackerString } from '@shared/utils/tracker'
|
||||
|
||||
export default class ConfigManager {
|
||||
constructor () {
|
||||
@@ -23,43 +36,58 @@ export default class ConfigManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Some aria2 conf
|
||||
* Aria2 Configuration Priority
|
||||
* system.json > built-in aria2.conf
|
||||
* https://aria2.github.io/manual/en/html/aria2c.html
|
||||
*
|
||||
* Best bt trackers
|
||||
* https://github.com/ngosang/trackerslist
|
||||
*/
|
||||
initSystemConfig () {
|
||||
this.systemConfig = new Store({
|
||||
name: 'system',
|
||||
cwd: getConfigBasePath(),
|
||||
/* eslint-disable quote-props */
|
||||
defaults: {
|
||||
'all-proxy': '',
|
||||
'allow-overwrite': true,
|
||||
'all-proxy': EMPTY_STRING,
|
||||
'allow-overwrite': false,
|
||||
'auto-file-renaming': true,
|
||||
'bt-tracker': tracker.join(','),
|
||||
'bt-exclude-tracker': EMPTY_STRING,
|
||||
'bt-force-encryption': false,
|
||||
'bt-load-saved-metadata': true,
|
||||
'bt-save-metadata': true,
|
||||
'bt-tracker': EMPTY_STRING,
|
||||
'continue': true,
|
||||
'dht-file-path': getDhtPath(4),
|
||||
'dht-file-path6': getDhtPath(6),
|
||||
'dht-file-path': getDhtPath(IP_VERSION.V4),
|
||||
'dht-file-path6': getDhtPath(IP_VERSION.V6),
|
||||
'dht-listen-port': 26701,
|
||||
'dir': getUserDownloadsPath(),
|
||||
'enable-dht6': true,
|
||||
'follow-metalink': true,
|
||||
'follow-torrent': true,
|
||||
'listen-port': 21301,
|
||||
'max-concurrent-downloads': 5,
|
||||
'max-connection-per-server': is.macOS() ? 64 : 16,
|
||||
'max-connection-per-server': getMaxConnectionPerServer(),
|
||||
'max-download-limit': 0,
|
||||
'max-overall-download-limit': 0,
|
||||
'max-overall-upload-limit': '128K',
|
||||
'min-split-size': '1M',
|
||||
'max-overall-upload-limit': 0,
|
||||
'no-proxy': EMPTY_STRING,
|
||||
'pause-metadata': false,
|
||||
'pause': true,
|
||||
'rpc-listen-port': 16800,
|
||||
'rpc-secret': '',
|
||||
'rpc-listen-port': ENGINE_RPC_PORT,
|
||||
'rpc-secret': EMPTY_STRING,
|
||||
'seed-ratio': 1,
|
||||
'seed-time': 60,
|
||||
'split': 16,
|
||||
'user-agent': 'Transmission/2.94'
|
||||
'split': getMaxConnectionPerServer(),
|
||||
'user-agent': CHROME_UA
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
})
|
||||
this.fixSystemConfig()
|
||||
}
|
||||
|
||||
initUserConfig () {
|
||||
this.userConfig = new Store({
|
||||
name: 'user',
|
||||
cwd: getConfigBasePath(),
|
||||
// Schema need electron-store upgrade to 3.x.x,
|
||||
// but it will cause the application build to fail.
|
||||
// schema: {
|
||||
@@ -68,36 +96,77 @@ export default class ConfigManager {
|
||||
// enum: ['auto', 'light', 'dark']
|
||||
// }
|
||||
// },
|
||||
/* eslint-disable quote-props */
|
||||
defaults: {
|
||||
'all-proxy-backup': '',
|
||||
'all-proxy-backup': EMPTY_STRING,
|
||||
'auto-check-update': is.macOS(),
|
||||
'auto-hide-window': false,
|
||||
'auto-sync-tracker': true,
|
||||
'enable-upnp': true,
|
||||
'engine-max-connection-per-server': getMaxConnectionPerServer(),
|
||||
'favorite-directories': [],
|
||||
'hide-app-menu': is.windows() || is.linux(),
|
||||
'history-directories': [],
|
||||
'keep-seeding': false,
|
||||
'keep-window-state': false,
|
||||
'last-check-update-time': 0,
|
||||
'last-sync-tracker-time': 0,
|
||||
'locale': app.getLocale(),
|
||||
'log-path': getLogPath(),
|
||||
'log-level': 'warn',
|
||||
'new-task-show-downloading': true,
|
||||
'no-confirm-before-delete-task': false,
|
||||
'open-at-login': false,
|
||||
'protocols': { 'magnet': true, 'thunder': false },
|
||||
'resume-all-when-app-launched': false,
|
||||
'keep-window-state': false,
|
||||
'session-path': getSessionPath(),
|
||||
'run-mode': APP_RUN_MODE.STANDARD,
|
||||
'show-progress-bar': true,
|
||||
'task-notification': true,
|
||||
'theme': 'auto',
|
||||
'theme': APP_THEME.AUTO,
|
||||
'tracker-source': [
|
||||
NGOSANG_TRACKERS_BEST_IP_URL_CDN,
|
||||
NGOSANG_TRACKERS_BEST_URL_CDN
|
||||
],
|
||||
'tray-theme': APP_THEME.AUTO,
|
||||
'tray-speedometer': is.macOS(),
|
||||
'update-channel': 'latest',
|
||||
'use-proxy': false,
|
||||
'window-state': {}
|
||||
}
|
||||
/* eslint-enable quote-props */
|
||||
})
|
||||
this.fixUserConfig()
|
||||
}
|
||||
|
||||
fixSystemConfig () {
|
||||
// Remove aria2c unrecognized options
|
||||
const { others } = separateConfig(this.systemConfig.store)
|
||||
if (!others) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.keys(others).forEach(key => {
|
||||
this.systemConfig.delete(key)
|
||||
})
|
||||
|
||||
// Fix spawn ENAMETOOLONG on Windows
|
||||
const tracker = reduceTrackerString(this.systemConfig.get('bt-tracker'))
|
||||
this.setSystemConfig('bt-tracker', tracker)
|
||||
}
|
||||
|
||||
fixUserConfig () {
|
||||
// Fix the value of open-at-login when the user delete
|
||||
// the Motrix self-starting item through startup management.
|
||||
const openAtLogin = app.getLoginItemSettings().openAtLogin
|
||||
const openAtLogin = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin
|
||||
if (this.getUserConfig('open-at-login') !== openAtLogin) {
|
||||
this.setUserConfig('open-at-login', openAtLogin)
|
||||
}
|
||||
|
||||
if (this.getUserConfig('tracker-source').length === 0) {
|
||||
this.setUserConfig('tracker-source', [
|
||||
NGOSANG_TRACKERS_BEST_IP_URL_CDN,
|
||||
NGOSANG_TRACKERS_BEST_URL_CDN
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
getSystemConfig (key, defaultValue) {
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
export default class Context {
|
||||
import logger from './Logger'
|
||||
import {
|
||||
getEnginePath,
|
||||
getAria2BinPath,
|
||||
getAria2ConfPath,
|
||||
getSessionPath
|
||||
} from '../utils'
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
export default class Context {
|
||||
constructor () {
|
||||
this.init()
|
||||
}
|
||||
|
||||
getLogPath () {
|
||||
const { path } = logger.transports.file.getFile()
|
||||
return path
|
||||
}
|
||||
|
||||
init () {
|
||||
// The key of Context cannot be the same as that of userConfig and systemConfig.
|
||||
this.context = {
|
||||
platform: platform,
|
||||
arch: arch,
|
||||
'log-path': this.getLogPath(),
|
||||
'session-path': getSessionPath(),
|
||||
'engine-path': getEnginePath(platform, arch),
|
||||
'aria2-bin-path': getAria2BinPath(platform, arch),
|
||||
'aria2-conf-path': getAria2ConfPath(platform, arch)
|
||||
}
|
||||
|
||||
logger.info('[Motrix] Context.init===>', this.context)
|
||||
}
|
||||
|
||||
get (key) {
|
||||
if (typeof key === 'undefined') {
|
||||
return this.context
|
||||
}
|
||||
|
||||
return this.context[key]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import { powerSaveBlocker } from 'electron'
|
||||
|
||||
let psbId = null
|
||||
import logger from './Logger'
|
||||
|
||||
let psbId
|
||||
export default class EnergyManager {
|
||||
startPowerSaveBlocker () {
|
||||
logger.info('[Motrix] EnergyManager.startPowerSaveBlocker', psbId)
|
||||
if (psbId && powerSaveBlocker.isStarted(psbId)) {
|
||||
return
|
||||
}
|
||||
|
||||
psbId = powerSaveBlocker.start('prevent-app-suspension')
|
||||
console.log('startPowerSaveBlocker===>', psbId)
|
||||
logger.info('[Motrix] start power save blocker:', psbId)
|
||||
}
|
||||
|
||||
stopPowerSaveBlocker () {
|
||||
if (!psbId || !powerSaveBlocker.isStarted(psbId)) {
|
||||
logger.info('[Motrix] EnergyManager.stopPowerSaveBlocker', psbId)
|
||||
if (typeof psbId === 'undefined' || !powerSaveBlocker.isStarted(psbId)) {
|
||||
return
|
||||
}
|
||||
|
||||
powerSaveBlocker.stop(psbId)
|
||||
console.log('stopPowerSaveBlocker===>', psbId)
|
||||
psbId = null
|
||||
logger.info('[Motrix] stop power save blocker:', psbId)
|
||||
psbId = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
import { app } from 'electron'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync, writeFile, unlink } from 'node:fs'
|
||||
import is from 'electron-is'
|
||||
import { existsSync } from 'fs'
|
||||
import { resolve, join } from 'path'
|
||||
import forever from 'forever-monitor'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
import {
|
||||
getEngineBin,
|
||||
getEnginePidPath,
|
||||
getAria2BinPath,
|
||||
getAria2ConfPath,
|
||||
getSessionPath,
|
||||
transformConfig
|
||||
} from '../utils/index'
|
||||
|
||||
const { platform, arch } = process
|
||||
|
||||
export default class Engine {
|
||||
// ChildProcess | null
|
||||
static instance = null
|
||||
|
||||
constructor (options = {}) {
|
||||
@@ -24,82 +26,99 @@ export default class Engine {
|
||||
this.userConfig = options.userConfig
|
||||
}
|
||||
|
||||
getStartSh () {
|
||||
const { platform } = process
|
||||
let basePath = resolve(app.getAppPath(), '..')
|
||||
start () {
|
||||
const pidPath = getEnginePidPath()
|
||||
logger.info('[Motrix] Engie pid path:', pidPath)
|
||||
|
||||
if (this.instance) {
|
||||
return
|
||||
}
|
||||
|
||||
const binPath = this.getEngineBinPath()
|
||||
const args = this.getStartArgs()
|
||||
this.instance = spawn(binPath, args, {
|
||||
windowsHide: false,
|
||||
stdio: is.dev() ? 'pipe' : 'ignore'
|
||||
})
|
||||
const pid = this.instance.pid.toString()
|
||||
this.writePidFile(pidPath, pid)
|
||||
|
||||
this.instance.once('close', () => {
|
||||
try {
|
||||
unlink(pidPath, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] Unlink engine process pid file failed: ${err}`)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn(`[Motrix] Unlink engine process pid file failed: ${err}`)
|
||||
}
|
||||
})
|
||||
|
||||
if (is.dev()) {
|
||||
basePath = resolve(__dirname, `../../../extra/${platform}`)
|
||||
}
|
||||
this.instance.stdout.on('data', (data) => {
|
||||
logger.log('[Motrix] engine stdout===>', data.toString())
|
||||
})
|
||||
|
||||
const binName = getEngineBin(platform)
|
||||
if (!binName) {
|
||||
throw new Error(this.i18n.t('app.engine-damaged-message'))
|
||||
this.instance.stderr.on('data', (data) => {
|
||||
logger.log('[Motrix] engine stderr===>', data.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let binPath = join(basePath, `/engine/${binName}`)
|
||||
const binIsExist = existsSync(binPath)
|
||||
stop () {
|
||||
logger.info('[Motrix] engine.stop.instance')
|
||||
if (this.instance) {
|
||||
this.instance.kill()
|
||||
this.instance = null
|
||||
}
|
||||
}
|
||||
|
||||
writePidFile (pidPath, pid) {
|
||||
writeFile(pidPath, pid, (err) => {
|
||||
if (err) {
|
||||
logger.error(`[Motrix] Write engine process pid failed: ${err}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getEngineBinPath () {
|
||||
const result = getAria2BinPath(platform, arch)
|
||||
const binIsExist = existsSync(result)
|
||||
if (!binIsExist) {
|
||||
logger.error('[Motrix] engine bin is not exist===>', binPath)
|
||||
logger.error('[Motrix] engine bin is not exist:', result)
|
||||
throw new Error(this.i18n.t('app.engine-missing-message'))
|
||||
}
|
||||
|
||||
let confPath = join(basePath, '/engine/aria2.conf')
|
||||
|
||||
let sessionPath = this.userConfig['session-path'] || getSessionPath()
|
||||
const sessionIsExist = existsSync(sessionPath)
|
||||
|
||||
let result = [`${binPath}`, `--conf-path=${confPath}`, `--save-session=${sessionPath}`]
|
||||
if (sessionIsExist) {
|
||||
result = [...result, `--input-file=${sessionPath}`]
|
||||
}
|
||||
|
||||
const extraConfig = transformConfig(this.systemConfig)
|
||||
result = [...result, ...extraConfig]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
start () {
|
||||
const sh = this.getStartSh()
|
||||
logger.info('[Motrix] Engine start sh===>', sh)
|
||||
this.instance = forever.start(sh, {
|
||||
max: 100,
|
||||
parser: function (command, args) {
|
||||
return {
|
||||
command: command,
|
||||
args: args
|
||||
}
|
||||
},
|
||||
silent: !is.dev()
|
||||
})
|
||||
getStartArgs () {
|
||||
const confPath = getAria2ConfPath(platform, arch)
|
||||
|
||||
const { child } = this.instance
|
||||
logger.info('[Motrix] Engine pid===>', child.pid)
|
||||
const sessionPath = getSessionPath()
|
||||
const sessionIsExist = existsSync(sessionPath)
|
||||
|
||||
this.instance.on('error', (err) => {
|
||||
logger.info(`[Motrix] Engine error===> ${err}`)
|
||||
})
|
||||
let result = [`--conf-path=${confPath}`, `--save-session=${sessionPath}`]
|
||||
if (sessionIsExist) {
|
||||
result = [...result, `--input-file=${sessionPath}`]
|
||||
}
|
||||
|
||||
this.instance.on('start', function (process, data) {
|
||||
logger.info(`[Motrix] Engine started===>`)
|
||||
})
|
||||
const extraConfig = {
|
||||
...this.systemConfig
|
||||
}
|
||||
const keepSeeding = this.userConfig['keep-seeding']
|
||||
const seedRatio = this.systemConfig['seed-ratio']
|
||||
if (keepSeeding || seedRatio === 0) {
|
||||
extraConfig['seed-ratio'] = 0
|
||||
delete extraConfig['seed-time']
|
||||
}
|
||||
console.log('extraConfig===>', extraConfig)
|
||||
|
||||
this.instance.on('stop', function (process) {
|
||||
logger.info(`[Motrix] Engine stopped===>`)
|
||||
})
|
||||
const extra = transformConfig(extraConfig)
|
||||
result = [...result, ...extra]
|
||||
|
||||
this.instance.on('restart', function (forever) {
|
||||
logger.info(`[Motrix] Engine exit===>`)
|
||||
})
|
||||
|
||||
this.instance.on('exit:code', function (code) {
|
||||
logger.info(`[Motrix] Engine exit===> ${code}`)
|
||||
})
|
||||
|
||||
// this.instance.on('stderr', (data) => {
|
||||
// logger.info(`[Motrix] Engine stderr===> ${data}`)
|
||||
// })
|
||||
return result
|
||||
}
|
||||
|
||||
isRunning (pid) {
|
||||
@@ -110,28 +129,6 @@ export default class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
stop () {
|
||||
const { pid } = this.instance.child
|
||||
try {
|
||||
logger.info('[Motrix] Engine stopping===>')
|
||||
this.instance.stop()
|
||||
} catch (err) {
|
||||
logger.error('[Motrix] Engine stop fail===>', err.message)
|
||||
this.forceStop(pid)
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
forceStop (pid) {
|
||||
try {
|
||||
if (pid && this.isRunning(pid)) {
|
||||
process.kill(pid)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] Engine forceStop fail===>', err)
|
||||
}
|
||||
}
|
||||
|
||||
restart () {
|
||||
this.stop()
|
||||
this.start()
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
'use strict'
|
||||
|
||||
import { Aria2 } from '@shared/aria2'
|
||||
|
||||
import logger from './Logger'
|
||||
import {
|
||||
compactUndefined,
|
||||
formatOptionsForEngine
|
||||
} from '@shared/utils'
|
||||
import {
|
||||
ENGINE_RPC_HOST,
|
||||
ENGINE_RPC_PORT,
|
||||
EMPTY_STRING
|
||||
} from '@shared/constants'
|
||||
|
||||
const defaults = {
|
||||
host: ENGINE_RPC_HOST,
|
||||
port: ENGINE_RPC_PORT,
|
||||
secret: EMPTY_STRING
|
||||
}
|
||||
|
||||
export default class EngineClient {
|
||||
static instance = null
|
||||
static client = null
|
||||
|
||||
constructor (options = {}) {
|
||||
this.options = {
|
||||
...defaults,
|
||||
...options
|
||||
}
|
||||
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.connect()
|
||||
}
|
||||
|
||||
connect () {
|
||||
logger.info('[Motrix] main engine client connect', this.options)
|
||||
const { host, port, secret } = this.options
|
||||
this.client = new Aria2({
|
||||
host,
|
||||
port,
|
||||
secret
|
||||
})
|
||||
}
|
||||
|
||||
async call (method, ...args) {
|
||||
return this.client.call(method, ...args).catch((err) => {
|
||||
logger.warn('[Motrix] call client fail:', err.message)
|
||||
})
|
||||
}
|
||||
|
||||
async changeGlobalOption (options) {
|
||||
logger.info('[Motrix] change engine global option:', options)
|
||||
const args = formatOptionsForEngine(options)
|
||||
|
||||
return this.call('changeGlobalOption', args)
|
||||
}
|
||||
|
||||
async shutdown (options = {}) {
|
||||
const { force = false } = options
|
||||
const { secret } = this.options
|
||||
|
||||
const method = force ? 'forceShutdown' : 'shutdown'
|
||||
const args = compactUndefined([secret])
|
||||
return this.call(method, ...args)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { app, dialog } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import logger from './Logger'
|
||||
|
||||
const defaults = {
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { join } from 'node:path'
|
||||
import is from 'electron-is'
|
||||
import logger from 'electron-log'
|
||||
|
||||
logger.transports.file.level = is.production() ? 'warn' : 'silly'
|
||||
logger.info('Logger init')
|
||||
import { IS_PORTABLE, PORTABLE_EXECUTABLE_DIR } from '@shared/constants'
|
||||
|
||||
const level = is.production() ? 'info' : 'silly'
|
||||
logger.transports.file.level = level
|
||||
|
||||
if (IS_PORTABLE) {
|
||||
logger.transports.file.resolvePath = () => join(PORTABLE_EXECUTABLE_DIR, 'main.log')
|
||||
}
|
||||
|
||||
logger.info('[Motrix] Logger init')
|
||||
logger.warn('[Motrix] Logger init')
|
||||
|
||||
export default logger
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { parse } from 'querystring'
|
||||
|
||||
import logger from './Logger'
|
||||
import protocolMap from '../configs/protocol'
|
||||
import { ADD_TASK_TYPE } from '@shared/constants'
|
||||
|
||||
export default class ProtocolManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
@@ -25,7 +28,11 @@ export default class ProtocolManager extends EventEmitter {
|
||||
this.setup(protocols)
|
||||
}
|
||||
|
||||
setup (protocols) {
|
||||
setup (protocols = {}) {
|
||||
if (is.dev() || is.mas()) {
|
||||
return
|
||||
}
|
||||
|
||||
Object.keys(protocols).forEach((protocol) => {
|
||||
const enabled = protocols[protocol]
|
||||
if (enabled) {
|
||||
@@ -41,7 +48,15 @@ export default class ProtocolManager extends EventEmitter {
|
||||
handle (url) {
|
||||
logger.info(`[Motrix] protocol url: ${url}`)
|
||||
|
||||
this.handleMagnetAndThunderProtocol(url)
|
||||
if (
|
||||
url.toLowerCase().startsWith('ftp:') ||
|
||||
url.toLowerCase().startsWith('http:') ||
|
||||
url.toLowerCase().startsWith('https:') ||
|
||||
url.toLowerCase().startsWith('magnet:') ||
|
||||
url.toLowerCase().startsWith('thunder:')
|
||||
) {
|
||||
return this.handleResourceProtocol(url)
|
||||
}
|
||||
|
||||
if (
|
||||
url.toLowerCase().startsWith('mo:') ||
|
||||
@@ -51,28 +66,20 @@ export default class ProtocolManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
handleMagnetAndThunderProtocol (url) {
|
||||
handleResourceProtocol (url) {
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
let protocolTag = ''
|
||||
|
||||
if (url.toLowerCase().startsWith('magnet:')) {
|
||||
protocolTag = 'handleMagnetProtocol'
|
||||
}
|
||||
|
||||
if (url.toLowerCase().startsWith('thunder:')) {
|
||||
protocolTag = 'handleThunderProtocol'
|
||||
}
|
||||
|
||||
logger.error(`[Motrix] ${protocolTag} url: ${url}`)
|
||||
|
||||
global.application.sendCommandToAll('application:new-task', 'uri', url)
|
||||
global.application.sendCommandToAll('application:new-task', {
|
||||
type: ADD_TASK_TYPE.URI,
|
||||
uri: url
|
||||
})
|
||||
}
|
||||
|
||||
handleMoProtocol (url) {
|
||||
const parsed = new URL(url)
|
||||
const { host } = parsed
|
||||
const { host, search } = parsed
|
||||
logger.info('[Motrix] protocol parsed:', parsed, host)
|
||||
|
||||
const command = protocolMap[host]
|
||||
@@ -80,10 +87,8 @@ export default class ProtocolManager extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
// @TODO 没想明白怎么传参数好
|
||||
// 如果按顺序传递,那 url 的 query string 就要求有序的了
|
||||
// const query = queryString.parse(parsed.query)
|
||||
const args = []
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
const query = search.startsWith('?') ? search.replace('?', '') : search
|
||||
const args = parse(query)
|
||||
global.application.sendCommandToAll(command, args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import NatAPI from '@motrix/nat-api'
|
||||
|
||||
import logger from './Logger'
|
||||
|
||||
let client = null
|
||||
const mappingStatus = {}
|
||||
|
||||
export default class UPnPManager {
|
||||
constructor (options = {}) {
|
||||
this.options = {
|
||||
...options
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
if (client) {
|
||||
return
|
||||
}
|
||||
|
||||
client = new NatAPI({
|
||||
autoUpdate: true
|
||||
})
|
||||
}
|
||||
|
||||
map (port) {
|
||||
this.init()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info('[Motrix] UPnPManager port mapping: ', port)
|
||||
if (!port) {
|
||||
reject(new Error('[Motrix] port was not specified'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
client.map(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager map ${port} failed, error: `, err.message)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
mappingStatus[port] = true
|
||||
logger.info(`[Motrix] UPnPManager port ${port} mapping succeeded`)
|
||||
resolve()
|
||||
})
|
||||
} catch (err) {
|
||||
reject(err.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unmap (port) {
|
||||
this.init()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info('[Motrix] UPnPManager port unmapping: ', port)
|
||||
if (!port) {
|
||||
reject(new Error('[Motrix] port was not specified'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!mappingStatus[port]) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
client.unmap(port, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] UPnPManager unmap ${port} failed, error: `, err)
|
||||
reject(err.message)
|
||||
return
|
||||
}
|
||||
|
||||
logger.info(`[Motrix] UPnPManager port ${port} unmapping succeeded`)
|
||||
mappingStatus[port] = false
|
||||
resolve()
|
||||
})
|
||||
} catch (err) {
|
||||
reject(err.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
closeClient () {
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
client.destroy(() => {
|
||||
client = null
|
||||
})
|
||||
} catch (err) {
|
||||
logger.warn('[Motrix] close UPnP client fail', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { resolve } from 'node:path'
|
||||
import { dialog } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { resolve } from 'path'
|
||||
|
||||
import logger from './Logger'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
|
||||
if (is.dev()) {
|
||||
autoUpdater.updateConfigPath = resolve(__dirname, '../../../app-update.yml')
|
||||
@@ -16,8 +17,10 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.options = options
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.isChecking = false
|
||||
this.updater = autoUpdater
|
||||
this.updater.autoDownload = false
|
||||
this.updater.autoInstallOnAppQuit = false
|
||||
this.updater.logger = logger
|
||||
this.autoCheckData = {
|
||||
checkEnable: this.options.autoCheck,
|
||||
@@ -39,22 +42,22 @@ export default class UpdateManager extends EventEmitter {
|
||||
this.updater.on('update-not-available', this.updateNotAvailable.bind(this))
|
||||
this.updater.on('download-progress', this.updateDownloadProgress.bind(this))
|
||||
this.updater.on('update-downloaded', this.updateDownloaded.bind(this))
|
||||
this.updater.on('update-cancelled', this.updateCancelled.bind(this))
|
||||
this.updater.on('error', this.updateError.bind(this))
|
||||
|
||||
if (this.autoCheckData.checkEnable) {
|
||||
if (this.autoCheckData.checkEnable && !this.isChecking) {
|
||||
this.autoCheckData.userCheck = false
|
||||
this.options.setCheckTime.setUserConfig('last-check-update-time', Date.now())
|
||||
this.updater.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
check () {
|
||||
this.options.setCheckTime.setUserConfig('last-check-update-time', Date.now())
|
||||
this.autoCheckData.userCheck = true
|
||||
this.updater.checkForUpdates()
|
||||
}
|
||||
|
||||
checkingForUpdate () {
|
||||
this.isChecking = true
|
||||
this.emit('checking')
|
||||
}
|
||||
|
||||
@@ -66,14 +69,17 @@ export default class UpdateManager extends EventEmitter {
|
||||
message: this.i18n.t('app.update-available-message'),
|
||||
buttons: [this.i18n.t('app.yes'), this.i18n.t('app.no')],
|
||||
cancelId: 1
|
||||
}, (buttonIndex) => {
|
||||
if (buttonIndex === 0) {
|
||||
}).then(({ response }) => {
|
||||
if (response === 0) {
|
||||
this.updater.downloadUpdate()
|
||||
} else {
|
||||
this.emit('update-cancelled', info)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateNotAvailable (event, info) {
|
||||
this.isChecking = false
|
||||
this.emit('update-not-available', info)
|
||||
if (this.autoCheckData.userCheck) {
|
||||
dialog.showMessageBox({
|
||||
@@ -102,21 +108,27 @@ export default class UpdateManager extends EventEmitter {
|
||||
dialog.showMessageBox({
|
||||
title: this.i18n.t('app.check-for-updates-title'),
|
||||
message: this.i18n.t('app.update-downloaded-message')
|
||||
}, () => {
|
||||
}).then(_ => {
|
||||
this.isChecking = false
|
||||
this.emit('will-updated')
|
||||
setImmediate(() => {
|
||||
setTimeout(() => {
|
||||
this.updater.quitAndInstall()
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
|
||||
updateCancelled () {
|
||||
this.isChecking = false
|
||||
}
|
||||
|
||||
updateError (event, error) {
|
||||
this.isChecking = false
|
||||
this.emit('update-error', error)
|
||||
const msg = (error == null)
|
||||
? this.i18n.t('update-error-message')
|
||||
? this.i18n.t('app.update-error-message')
|
||||
: (error.stack || error).toString()
|
||||
|
||||
this.updater.logger.warn(`[Motrix] update-error: ${msg}`)
|
||||
dialog.showErrorBox(msg)
|
||||
dialog.showErrorBox('Error', msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,8 @@
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// Install `electron-debug` with `devtron`
|
||||
require('electron-debug')({
|
||||
// devToolsMode: 'right',
|
||||
showDevTools: true
|
||||
})
|
||||
|
||||
// Install `vue-devtools`
|
||||
require('electron').app.on('ready', () => {
|
||||
require('electron').app.whenReady().then(() => {
|
||||
let installExtension = require('electron-devtools-installer')
|
||||
installExtension.default(installExtension.VUEJS_DEVTOOLS)
|
||||
.then(() => {})
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { app } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { initialize } from '@electron/remote/main'
|
||||
|
||||
import Launcher from './Launcher'
|
||||
|
||||
/**
|
||||
* initialize the main-process side of the remote module
|
||||
*/
|
||||
initialize()
|
||||
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"id": "menu.app",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
@@ -17,10 +17,11 @@
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.task-list", "command": "application:task-list" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
@@ -29,6 +30,7 @@
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "id": "task.select-all-task", "command": "application:select-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": { "page": "index" } },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
@@ -15,10 +15,11 @@
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.task-list", "command": "application:task-list" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
@@ -26,7 +27,8 @@
|
||||
{ "id": "task.move-task-down", "command": "application:move-task-down" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" }
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "id": "task.select-all-task", "command": "application:select-all-task" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"type": "button", "icon": "new-task", "id": "task.new-task", "command": "application:new-task", "command-arg": "uri", "command-after": "application:show,index"
|
||||
"type": "button", "icon": "new-task", "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index"
|
||||
},
|
||||
{
|
||||
"type": "spacer", "size": "small"
|
||||
@@ -10,13 +10,13 @@
|
||||
"id": "task.task-list",
|
||||
"items": [
|
||||
{
|
||||
"type": "button", "icon": "task-active", "command": "application:task-list", "command-arg": "active"
|
||||
"type": "button", "icon": "task-active", "command": "application:task-list", "command-arg": { "status": "active" }
|
||||
},
|
||||
{
|
||||
"type": "button", "icon": "task-waiting", "command": "application:task-list", "command-arg": "waiting"
|
||||
"type": "button", "icon": "task-waiting", "command": "application:task-list", "command-arg": { "status": "waiting" }
|
||||
},
|
||||
{
|
||||
"type": "button", "icon": "task-stopped", "command": "application:task-list", "command-arg": "stopped"
|
||||
"type": "button", "icon": "task-stopped", "command": "application:task-list", "command-arg": { "status": "stopped" }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -30,6 +30,6 @@
|
||||
"type": "spacer", "size": "small"
|
||||
},
|
||||
{
|
||||
"type": "button", "icon": "about", "id": "app.about", "command": "application:about", "command-before": "application:show,index"
|
||||
"type": "button", "icon": "about", "id": "app.about", "command": "application:about", "command-before": "application:show?page=index"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
[
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": { "type": "torrent" }, "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": { "page": "index" } },
|
||||
{ "id": "help.manual", "command": "help:manual" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences", "command-before": "application:show,index" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
{ "id": "app.task-list", "command": "application:task-list", "command-before": "application:show?page=index" },
|
||||
{ "id": "app.preferences", "command": "application:preferences", "command-before": "application:show?page=index" },
|
||||
{ "id": "app.quit", "command": "application:quit" }
|
||||
]
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
{
|
||||
"id": "menu.file",
|
||||
"submenu": [
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show,index" },
|
||||
{ "id": "app.about", "command": "application:about", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.preferences", "command": "application:preferences" },
|
||||
{ "id": "app.check-for-updates", "command": "application:check-for-updates" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": "index" },
|
||||
{ "id": "app.show", "command": "application:show", "command-arg": { "page": "index" } },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.quit", "role": "quit" }
|
||||
]
|
||||
@@ -15,10 +15,11 @@
|
||||
{
|
||||
"id": "menu.task",
|
||||
"submenu": [
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show,index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-arg": "torrent", "command-after": "application:show,index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show,index" },
|
||||
{ "id": "task.new-task", "command": "application:new-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.new-bt-task", "command": "application:new-bt-task", "command-after": "application:show?page=index" },
|
||||
{ "id": "task.open-file", "command": "application:open-file", "command-before": "application:show?page=index" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "app.task-list", "command": "application:task-list" },
|
||||
{ "id": "task.pause-task", "command": "application:pause-task" },
|
||||
{ "id": "task.resume-task", "command": "application:resume-task" },
|
||||
{ "id": "task.delete-task", "command": "application:delete-task" },
|
||||
@@ -27,6 +28,7 @@
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.pause-all-task", "command": "application:pause-all-task" },
|
||||
{ "id": "task.resume-all-task", "command": "application:resume-all-task" },
|
||||
{ "id": "task.select-all-task", "command": "application:select-all-task" },
|
||||
{ "type": "separator" },
|
||||
{ "id": "task.clear-recent-tasks", "command": "application:clear-recent-tasks" }
|
||||
]
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import is from 'electron-is'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { app } from 'electron'
|
||||
|
||||
import { bytesToSize } from '@shared/utils'
|
||||
|
||||
import {
|
||||
APP_RUN_MODE
|
||||
} from '@shared/constants'
|
||||
|
||||
const enabled = is.macOS()
|
||||
|
||||
export default class DockManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
super()
|
||||
this.options = options
|
||||
const { runMode } = this.options
|
||||
if (runMode === APP_RUN_MODE.TRAY) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
show = enabled
|
||||
? () => {
|
||||
if (app.dock.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
return app.dock.show()
|
||||
}
|
||||
: () => {}
|
||||
|
||||
hide = enabled
|
||||
? () => {
|
||||
if (!app.dock.isVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
app.dock.hide()
|
||||
}
|
||||
: () => {}
|
||||
|
||||
// macOS setBadge not working
|
||||
// @see https://github.com/electron/electron/issues/25745#issuecomment-702826143
|
||||
setBadge = enabled
|
||||
? (text) => {
|
||||
app.dock.setBadge(text)
|
||||
}
|
||||
: (text) => {}
|
||||
|
||||
handleSpeedChange = enabled
|
||||
? (speed) => {
|
||||
const { downloadSpeed } = speed
|
||||
const text = downloadSpeed > 0 ? `${bytesToSize(downloadSpeed)}/s` : ''
|
||||
this.setBadge(text)
|
||||
}
|
||||
: (text) => {}
|
||||
|
||||
openDock = enabled
|
||||
? (path) => {
|
||||
app.dock.downloadFinished(path)
|
||||
}
|
||||
: (path) => {}
|
||||
}
|
||||
@@ -5,20 +5,20 @@ const localeManager = new LocaleManager({
|
||||
resources
|
||||
})
|
||||
|
||||
export function getLocaleManager () {
|
||||
export const getLocaleManager = () => {
|
||||
return localeManager
|
||||
}
|
||||
|
||||
export function setupLocaleManager (locale) {
|
||||
export const setupLocaleManager = (locale) => {
|
||||
localeManager.changeLanguageByLocale(locale)
|
||||
|
||||
return localeManager
|
||||
}
|
||||
|
||||
export function getI18n () {
|
||||
export const getI18n = () => {
|
||||
return localeManager.getI18n()
|
||||
}
|
||||
|
||||
export function getI18nTranslator () {
|
||||
export const getI18nTranslator = () => {
|
||||
return localeManager.getI18n().t
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { Menu } from 'electron'
|
||||
|
||||
import keymap from '@shared/keymap'
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import keymap from '@shared/keymap'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { getI18n } from '../ui/Locale'
|
||||
|
||||
export default class MenuManager extends EventEmitter {
|
||||
constructor (options) {
|
||||
@@ -23,13 +24,13 @@ export default class MenuManager extends EventEmitter {
|
||||
}
|
||||
|
||||
load () {
|
||||
let template = require(`../menus/${process.platform}.json`)
|
||||
this.template = template['menu']
|
||||
const template = require(`../menus/${process.platform}.json`)
|
||||
this.template = template.menu
|
||||
}
|
||||
|
||||
build () {
|
||||
const keystrokesByCommand = {}
|
||||
for (let item in this.keymap) {
|
||||
for (const item in this.keymap) {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ export default class MenuManager extends EventEmitter {
|
||||
this.items = flattenMenuItems(menu)
|
||||
}
|
||||
|
||||
rebuild () {
|
||||
handleLocaleChange (locale) {
|
||||
this.setup()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { systemPreferences } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { nativeTheme } from 'electron'
|
||||
|
||||
import { APP_THEME } from '@shared/constants'
|
||||
import logger from '../core/Logger'
|
||||
import { getSystemTheme } from '../utils'
|
||||
|
||||
export default class ThemeManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.options = options
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.systemTheme = getSystemTheme()
|
||||
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
getSystemTheme () {
|
||||
let result = LIGHT_THEME
|
||||
if (!is.macOS()) {
|
||||
return result
|
||||
}
|
||||
result = systemPreferences.isDarkMode() ? DARK_THEME : LIGHT_THEME
|
||||
return result
|
||||
return this.systemTheme
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
systemPreferences.subscribeNotification(
|
||||
'AppleInterfaceThemeChangedNotification',
|
||||
() => {
|
||||
const theme = this.getSystemTheme()
|
||||
this.updateAppAppearance(theme)
|
||||
this.emit('system-theme-changed', theme)
|
||||
}
|
||||
)
|
||||
nativeTheme.on('updated', () => {
|
||||
const theme = getSystemTheme()
|
||||
this.systemTheme = theme
|
||||
logger.info('[Motrix] nativeTheme updated===>', theme)
|
||||
this.emit('system-theme-change', theme)
|
||||
})
|
||||
}
|
||||
|
||||
updateAppAppearance (theme) {
|
||||
if (!is.macOS() || theme !== LIGHT_THEME || theme !== DARK_THEME) {
|
||||
return
|
||||
}
|
||||
systemPreferences.setAppLevelAppearance(theme)
|
||||
updateSystemTheme (theme) {
|
||||
theme = theme === APP_THEME.AUTO ? 'system' : theme
|
||||
nativeTheme.themeSource = theme
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'path'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { join } from 'node:path'
|
||||
import { TouchBar, nativeImage } from 'electron'
|
||||
|
||||
import { handleCommand } from '../utils/menu'
|
||||
import logger from '../core/Logger'
|
||||
|
||||
@@ -15,7 +16,7 @@ export default class TouchBarManager extends EventEmitter {
|
||||
}
|
||||
|
||||
load () {
|
||||
this.template = require(`../menus/touchBar.json`)
|
||||
this.template = require('../menus/touchBar.json')
|
||||
}
|
||||
|
||||
getClickFn (item) {
|
||||
@@ -41,30 +42,32 @@ export default class TouchBarManager extends EventEmitter {
|
||||
const { label, backgroundColor, textColor, size } = options
|
||||
|
||||
switch (type) {
|
||||
case 'button':
|
||||
const icon = this.getIconImage(options.icon)
|
||||
const click = this.getClickFn(options)
|
||||
result = new TouchBarButton({
|
||||
label,
|
||||
backgroundColor,
|
||||
icon,
|
||||
click
|
||||
case 'button':
|
||||
result = new TouchBarButton({
|
||||
label,
|
||||
backgroundColor,
|
||||
icon: this.getIconImage(options.icon),
|
||||
click: this.getClickFn(options)
|
||||
})
|
||||
break
|
||||
case 'label':
|
||||
result = new TouchBarLabel({
|
||||
label,
|
||||
textColor
|
||||
})
|
||||
break
|
||||
case 'spacer':
|
||||
result = new TouchBarSpacer({ size })
|
||||
break
|
||||
case 'group':
|
||||
result = new TouchBarGroup({
|
||||
items: new TouchBar({
|
||||
items: options.items
|
||||
})
|
||||
break
|
||||
case 'label':
|
||||
result = new TouchBarLabel({
|
||||
label,
|
||||
textColor
|
||||
})
|
||||
break
|
||||
case 'spacer':
|
||||
result = new TouchBarSpacer({ size })
|
||||
break
|
||||
case 'group':
|
||||
result = new TouchBarGroup({ items: options.items })
|
||||
break
|
||||
default:
|
||||
result = null
|
||||
})
|
||||
break
|
||||
default:
|
||||
result = null
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -90,7 +93,7 @@ export default class TouchBarManager extends EventEmitter {
|
||||
if (!bar) {
|
||||
try {
|
||||
const items = this.build(this.template)
|
||||
bar = new TouchBar(items)
|
||||
bar = new TouchBar({ items })
|
||||
this.bars[page] = bar
|
||||
} catch (e) {
|
||||
logger.info('getTouchBarByPage fail', e)
|
||||
|
||||
@@ -1,47 +1,133 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'path'
|
||||
import { Tray, Menu, systemPreferences } from 'electron'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { join } from 'node:path'
|
||||
import { Tray, Menu, nativeImage } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import { APP_RUN_MODE, APP_THEME } from '@shared/constants'
|
||||
import { getInverseTheme } from '@shared/utils'
|
||||
import logger from '../core/Logger'
|
||||
import { getI18n } from './Locale'
|
||||
import {
|
||||
translateTemplate,
|
||||
flattenMenuItems,
|
||||
updateStates
|
||||
} from '../utils/menu'
|
||||
import { getI18n } from '@/ui/Locale'
|
||||
import { LIGHT_THEME, DARK_THEME } from '@shared/constants'
|
||||
import { convertArrayBufferToBuffer } from '../utils/index'
|
||||
|
||||
let tray = null
|
||||
const { platform } = process
|
||||
|
||||
export default class TrayManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
|
||||
this.options = options
|
||||
this.theme = options.theme || APP_THEME.AUTO
|
||||
|
||||
this.systemTheme = options.systemTheme
|
||||
this.inverseSystemTheme = getInverseTheme(this.systemTheme)
|
||||
this.macOS = platform === 'darwin'
|
||||
|
||||
this.speedometer = options.speedometer
|
||||
this.runMode = options.runMode
|
||||
|
||||
this.i18n = getI18n()
|
||||
|
||||
this.menu = null
|
||||
this.cache = {}
|
||||
|
||||
this.uploadSpeed = 0
|
||||
this.downloadSpeed = 0
|
||||
this.status = false
|
||||
this.focused = false
|
||||
this.initialized = false
|
||||
|
||||
this.load()
|
||||
this.init()
|
||||
this.setup()
|
||||
this.handleEvents()
|
||||
}
|
||||
|
||||
load () {
|
||||
this.template = require(`../menus/tray.json`)
|
||||
const theme = systemPreferences.isDarkMode() ? DARK_THEME : LIGHT_THEME
|
||||
init () {
|
||||
if (tray || this.initialized || this.runMode === APP_RUN_MODE.HIDE_TRAY) {
|
||||
return
|
||||
}
|
||||
|
||||
if (is.macOS()) {
|
||||
this.normalIcon = join(__static, `./mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = join(__static, `./mo-tray-${theme}-active.png`)
|
||||
} else {
|
||||
this.normalIcon = join(__static, './mo-tray-colorful-normal.png')
|
||||
this.activeIcon = join(__static, './mo-tray-colorful-active.png')
|
||||
this.loadTemplate()
|
||||
this.loadImages()
|
||||
this.initTray()
|
||||
this.setupMenu()
|
||||
this.bindEvents()
|
||||
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
loadTemplate () {
|
||||
this.template = require('../menus/tray.json')
|
||||
}
|
||||
|
||||
loadImages () {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
this.loadImagesForMacOS()
|
||||
break
|
||||
case 'win32':
|
||||
this.loadImagesForWindows()
|
||||
break
|
||||
case 'linux':
|
||||
this.loadImagesForLinux()
|
||||
break
|
||||
|
||||
default:
|
||||
this.loadImagesForDefault()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
build () {
|
||||
loadImagesForMacOS () {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')
|
||||
}
|
||||
|
||||
loadImagesForWindows () {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-colorful-normal.png')
|
||||
this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-colorful-active.png')
|
||||
}
|
||||
|
||||
loadImagesForLinux () {
|
||||
const { theme } = this
|
||||
if (theme === APP_THEME.AUTO) {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-dark-normal.png')
|
||||
this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-dark-active.png')
|
||||
} else {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage(`mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = this.getFromCacheOrCreateImage(`mo-tray-${theme}-active.png`)
|
||||
}
|
||||
}
|
||||
|
||||
loadImagesForDefault () {
|
||||
this.normalIcon = this.getFromCacheOrCreateImage('mo-tray-light-normal.png')
|
||||
this.activeIcon = this.getFromCacheOrCreateImage('mo-tray-light-active.png')
|
||||
}
|
||||
|
||||
getFromCacheOrCreateImage (key) {
|
||||
let file = this.getCache(key)
|
||||
if (file) {
|
||||
return file
|
||||
}
|
||||
|
||||
file = nativeImage.createFromPath(join(__static, `./${key}`))
|
||||
file.setTemplateImage(this.macOS)
|
||||
this.setCache(key, file)
|
||||
return file
|
||||
}
|
||||
|
||||
getCache (key) {
|
||||
return this.cache[key]
|
||||
}
|
||||
|
||||
setCache (key, value) {
|
||||
this.cache[key] = value
|
||||
}
|
||||
|
||||
buildMenu () {
|
||||
const keystrokesByCommand = {}
|
||||
for (let item in this.keymap) {
|
||||
for (const item in this.keymap) {
|
||||
keystrokesByCommand[this.keymap[item]] = item
|
||||
}
|
||||
|
||||
@@ -52,74 +138,145 @@ export default class TrayManager extends EventEmitter {
|
||||
this.items = flattenMenuItems(this.menu)
|
||||
}
|
||||
|
||||
setup () {
|
||||
this.build()
|
||||
setupMenu () {
|
||||
this.buildMenu()
|
||||
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (process.platform === 'linux') {
|
||||
tray.setContextMenu(this.menu)
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
initTray () {
|
||||
const { icon } = this.getIcons()
|
||||
tray = new Tray(icon)
|
||||
// tray.setPressedImage(inverseIcon)
|
||||
|
||||
if (!this.macOS) {
|
||||
tray.setToolTip('Motrix')
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
tray = new Tray(this.normalIcon)
|
||||
tray.setToolTip('Motrix')
|
||||
bindEvents () {
|
||||
// All OS
|
||||
tray.on('click', this.handleTrayClick)
|
||||
|
||||
// macOS, Windows
|
||||
// tray.on('double-click', this.handleTrayDbClick)
|
||||
tray.on('right-click', this.handleTrayRightClick)
|
||||
tray.on('mouse-down', this.handleTrayMouseDown)
|
||||
tray.on('mouse-up', this.handleTrayMouseUp)
|
||||
|
||||
// macOS only
|
||||
tray.setIgnoreDoubleClickEvents(true)
|
||||
tray.on('drop-files', this.handleTrayDropFiles)
|
||||
tray.on('drop-text', this.handleTrayDropText)
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
tray.on('click', this.handleTrayClick)
|
||||
tray.on('double-click', this.handleTrayDbClick)
|
||||
tray.on('right-click', this.handleTrayRightClick)
|
||||
unbindEvents () {
|
||||
// All OS
|
||||
tray.removeListener('click', this.handleTrayClick)
|
||||
|
||||
tray.on('drop-files', this.handleTrayDropFile)
|
||||
// macOS, Windows
|
||||
tray.removeListener('right-click', this.handleTrayRightClick)
|
||||
tray.removeListener('mouse-down', this.handleTrayMouseDown)
|
||||
tray.removeListener('mouse-up', this.handleTrayMouseUp)
|
||||
|
||||
// macOS only
|
||||
tray.removeListener('drop-files', this.handleTrayDropFiles)
|
||||
tray.removeListener('drop-text', this.handleTrayDropText)
|
||||
}
|
||||
|
||||
handleTrayClick = (event) => {
|
||||
event.preventDefault()
|
||||
global.application.toggle()
|
||||
}
|
||||
|
||||
handleTrayDbClick = (event) => {
|
||||
event.preventDefault()
|
||||
global.application.show()
|
||||
}
|
||||
|
||||
handleTrayRightClick = (event) => {
|
||||
event.preventDefault()
|
||||
tray.popUpContextMenu(this.menu)
|
||||
}
|
||||
|
||||
handleTrayDropFile = (event, files) => {
|
||||
global.application.show()
|
||||
global.application.handleFile(files[0])
|
||||
handleTrayMouseDown = (event) => {
|
||||
this.focused = true
|
||||
this.emit('mouse-down', {
|
||||
focused: true,
|
||||
theme: this.inverseSystemTheme
|
||||
})
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
updateStatus (status) {
|
||||
this.status = status
|
||||
this.updateIcon()
|
||||
handleTrayMouseUp = (event) => {
|
||||
this.focused = false
|
||||
this.emit('mouse-up', {
|
||||
focused: false,
|
||||
theme: this.theme
|
||||
})
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
updateIcon () {
|
||||
const icon = this.status ? this.activeIcon : this.normalIcon
|
||||
tray.setImage(icon)
|
||||
handleTrayDropFiles = (event, files) => {
|
||||
this.emit('drop-files', files)
|
||||
}
|
||||
|
||||
changeIconTheme (theme = LIGHT_THEME) {
|
||||
if (!is.macOS()) {
|
||||
handleTrayDropText = (event, text) => {
|
||||
this.emit('drop-text', text)
|
||||
}
|
||||
|
||||
toggleSpeedometer (enabled) {
|
||||
this.speedometer = enabled
|
||||
}
|
||||
|
||||
async renderTray () {
|
||||
if (!tray || this.speedometer) {
|
||||
return
|
||||
}
|
||||
|
||||
this.normalIcon = join(__static, `./mo-tray-${theme}-normal.png`)
|
||||
this.activeIcon = join(__static, `./mo-tray-${theme}-active.png`)
|
||||
const { icon } = this.getIcons()
|
||||
|
||||
this.updateIcon()
|
||||
tray.setImage(icon)
|
||||
// tray.setPressedImage(inverseIcon)
|
||||
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
getIcons () {
|
||||
if (this.macOS) {
|
||||
return { icon: this.normalIcon }
|
||||
}
|
||||
|
||||
const { focused, status, systemTheme } = this
|
||||
|
||||
const icon = status ? this.activeIcon : this.normalIcon
|
||||
if (systemTheme === APP_THEME.DARK) {
|
||||
return {
|
||||
icon
|
||||
}
|
||||
}
|
||||
|
||||
const inverseIcon = status ? this.inverseActiveIcon : this.inverseNormalIcon
|
||||
|
||||
return {
|
||||
icon: focused ? inverseIcon : icon
|
||||
// inverseIcon: focused ? icon : inverseIcon
|
||||
}
|
||||
}
|
||||
|
||||
updateContextMenu () {
|
||||
/**
|
||||
* Linux requires setContextMenu to be called
|
||||
* in order for the context menu to populate correctly
|
||||
*/
|
||||
if (!tray || process.platform !== 'linux') {
|
||||
return
|
||||
}
|
||||
|
||||
tray.setContextMenu(this.menu)
|
||||
}
|
||||
|
||||
updateMenuStates (visibleStates, enabledStates, checkedStates) {
|
||||
updateStates(this.items, visibleStates, enabledStates, checkedStates)
|
||||
|
||||
this.updateContextMenu()
|
||||
}
|
||||
|
||||
updateMenuItemVisibleState (id, flag) {
|
||||
@@ -136,7 +293,77 @@ export default class TrayManager extends EventEmitter {
|
||||
this.updateMenuStates(null, enabledStates, null)
|
||||
}
|
||||
|
||||
handleLocaleChange (locale) {
|
||||
this.setupMenu()
|
||||
}
|
||||
|
||||
handleRunModeChange (mode) {
|
||||
this.runMode = mode
|
||||
|
||||
if (mode === APP_RUN_MODE.HIDE_TRAY) {
|
||||
this.destroy()
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|
||||
handleSpeedometerEnableChange (enabled) {
|
||||
this.toggleSpeedometer(enabled)
|
||||
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
handleSystemThemeChange (systemTheme = APP_THEME.LIGHT) {
|
||||
if (!is.macOS()) {
|
||||
return
|
||||
}
|
||||
|
||||
this.systemTheme = systemTheme
|
||||
this.inverseSystemTheme = getInverseTheme(systemTheme)
|
||||
|
||||
this.loadImages()
|
||||
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
handleDownloadStatusChange (status) {
|
||||
this.status = status
|
||||
|
||||
this.renderTray()
|
||||
}
|
||||
|
||||
async handleSpeedChange ({ uploadSpeed, downloadSpeed }) {
|
||||
if (!this.speedometer) {
|
||||
return
|
||||
}
|
||||
|
||||
this.uploadSpeed = uploadSpeed
|
||||
this.downloadSpeed = downloadSpeed
|
||||
|
||||
await this.renderTray()
|
||||
}
|
||||
|
||||
async updateTrayByImage (ab) {
|
||||
if (!tray) {
|
||||
return
|
||||
}
|
||||
|
||||
const buffer = convertArrayBufferToBuffer(ab)
|
||||
const image = nativeImage.createFromBuffer(buffer, {
|
||||
scaleFactor: 2
|
||||
})
|
||||
image.setTemplateImage(this.macOS)
|
||||
tray.setImage(image)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
logger.info('[Motrix] TrayManager.destroy')
|
||||
if (tray) {
|
||||
this.unbindEvents()
|
||||
}
|
||||
|
||||
tray.destroy()
|
||||
tray = null
|
||||
this.initialized = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
import { join } from 'path'
|
||||
import { EventEmitter } from 'events'
|
||||
import { join } from 'node:path'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { debounce } from 'lodash'
|
||||
import { app, shell, screen, BrowserWindow } from 'electron'
|
||||
import is from 'electron-is'
|
||||
|
||||
import pageConfig from '../configs/page'
|
||||
import logger from '../core/Logger'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
const defaultBrowserOptions = {
|
||||
const baseBrowserOptions = {
|
||||
titleBarStyle: 'hiddenInset',
|
||||
show: false,
|
||||
width: 1024,
|
||||
height: 768,
|
||||
backgroundColor: '#fff',
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
}
|
||||
|
||||
// fix: BrowserWindow rendering bug under linux
|
||||
const defaultBrowserOptions = is.macOS()
|
||||
? {
|
||||
...baseBrowserOptions,
|
||||
vibrancy: 'ultra-dark',
|
||||
visualEffectState: 'active',
|
||||
backgroundColor: '#00000000'
|
||||
}
|
||||
: {
|
||||
...baseBrowserOptions
|
||||
}
|
||||
|
||||
export default class WindowManager extends EventEmitter {
|
||||
constructor (options = {}) {
|
||||
super()
|
||||
@@ -71,7 +85,7 @@ export default class WindowManager extends EventEmitter {
|
||||
openWindow (page, options = {}) {
|
||||
const pageOptions = this.getPageOptions(page)
|
||||
const { hidden } = options
|
||||
|
||||
const autoHideWindow = this.userConfig['auto-hide-window']
|
||||
let window = this.windows[page] || null
|
||||
if (window) {
|
||||
window.show()
|
||||
@@ -81,18 +95,27 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
window = new BrowserWindow({
|
||||
...defaultBrowserOptions,
|
||||
...pageOptions.attrs
|
||||
...pageOptions.attrs,
|
||||
webPreferences: {
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true
|
||||
}
|
||||
})
|
||||
|
||||
const bounds = this.getPageBounds(page)
|
||||
console.log('bounds ====>', bounds)
|
||||
if (bounds) {
|
||||
window.setBounds(bounds)
|
||||
}
|
||||
|
||||
window.webContents.on('new-window', (e, url) => {
|
||||
e.preventDefault()
|
||||
if (is.dev() && pageOptions.openDevTools) {
|
||||
window.webContents.openDevTools()
|
||||
}
|
||||
|
||||
window.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
if (pageOptions.url) {
|
||||
@@ -105,6 +128,14 @@ export default class WindowManager extends EventEmitter {
|
||||
}
|
||||
})
|
||||
|
||||
window.on('enter-full-screen', () => {
|
||||
this.emit('enter-full-screen', window)
|
||||
})
|
||||
|
||||
window.on('leave-full-screen', () => {
|
||||
this.emit('leave-full-screen', window)
|
||||
})
|
||||
|
||||
this.handleWindowState(page, window)
|
||||
|
||||
this.handleWindowClose(pageOptions, page, window)
|
||||
@@ -112,6 +143,10 @@ export default class WindowManager extends EventEmitter {
|
||||
this.bindAfterClosed(page, window)
|
||||
|
||||
this.addWindow(page, window)
|
||||
if (autoHideWindow) {
|
||||
this.handleWindowBlur()
|
||||
}
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
@@ -133,6 +168,10 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
destroyWindow (page) {
|
||||
const win = this.getWindow(page)
|
||||
if (!win) {
|
||||
return
|
||||
}
|
||||
|
||||
this.removeWindow(page)
|
||||
win.removeListener('closed')
|
||||
win.removeListener('move')
|
||||
@@ -166,7 +205,15 @@ export default class WindowManager extends EventEmitter {
|
||||
window.on('close', (event) => {
|
||||
if (pageOptions.bindCloseToHide && !this.willQuit) {
|
||||
event.preventDefault()
|
||||
window.hide()
|
||||
|
||||
// @see https://github.com/electron/electron/issues/20263
|
||||
if (window.isFullScreen()) {
|
||||
window.once('leave-full-screen', () => window.hide())
|
||||
|
||||
window.setFullScreen(false)
|
||||
} else {
|
||||
window.hide()
|
||||
}
|
||||
}
|
||||
const bounds = window.getBounds()
|
||||
this.emit('window-closed', { page, bounds })
|
||||
@@ -175,15 +222,16 @@ export default class WindowManager extends EventEmitter {
|
||||
|
||||
showWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
if (!window || (window.isVisible() && !window.isMinimized())) {
|
||||
return
|
||||
}
|
||||
|
||||
window.show()
|
||||
}
|
||||
|
||||
hideWindow (page) {
|
||||
const window = this.getWindow(page)
|
||||
if (!window) {
|
||||
if (!window || !window.isVisible()) {
|
||||
return
|
||||
}
|
||||
window.hide()
|
||||
@@ -200,10 +248,11 @@ export default class WindowManager extends EventEmitter {
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
if (window.isVisible()) {
|
||||
window.hide()
|
||||
} else {
|
||||
|
||||
if (!window.isVisible() || window.isFullScreen()) {
|
||||
window.show()
|
||||
} else {
|
||||
window.hide()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +266,18 @@ export default class WindowManager extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
onWindowBlur (event, window) {
|
||||
window.hide()
|
||||
}
|
||||
|
||||
handleWindowBlur () {
|
||||
app.on('browser-window-blur', this.onWindowBlur)
|
||||
}
|
||||
|
||||
unbindWindowBlur () {
|
||||
app.removeListener('browser-window-blur', this.onWindowBlur)
|
||||
}
|
||||
|
||||
handleAllWindowClosed () {
|
||||
app.on('window-all-closed', (event) => {
|
||||
event.preventDefault()
|
||||
@@ -227,7 +288,7 @@ export default class WindowManager extends EventEmitter {
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
logger.info('[Motrix] sendCommandTo===>', command, ...args)
|
||||
logger.info('[Motrix] send command to:', command, ...args)
|
||||
window.webContents.send('command', command, ...args)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,91 @@
|
||||
import { app } from 'electron'
|
||||
import { resolve } from 'node:path'
|
||||
import { access, constants, existsSync, lstatSync } from 'node:fs'
|
||||
import { app, nativeTheme, shell } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { resolve } from 'path'
|
||||
import { existsSync, lstatSync } from 'fs'
|
||||
|
||||
import {
|
||||
APP_THEME,
|
||||
ENGINE_MAX_CONNECTION_PER_SERVER,
|
||||
IP_VERSION,
|
||||
IS_PORTABLE,
|
||||
PORTABLE_EXECUTABLE_DIR
|
||||
} from '@shared/constants'
|
||||
import { engineBinMap, engineArchMap } from '../configs/engine'
|
||||
import logger from '../core/Logger'
|
||||
import engineBinMap from '../configs/engine'
|
||||
|
||||
export function getLogPath () {
|
||||
return logger.transports.file.file
|
||||
export const getUserDataPath = () => {
|
||||
return IS_PORTABLE ? PORTABLE_EXECUTABLE_DIR : app.getPath('userData')
|
||||
}
|
||||
|
||||
export function getDhtPath (protocol) {
|
||||
const name = protocol === 6 ? 'dht6.dat' : 'dht.dat'
|
||||
return resolve(app.getPath('userData'), `./${name}`)
|
||||
export const getSystemLogPath = () => {
|
||||
return app.getPath('logs')
|
||||
}
|
||||
|
||||
export function getSessionPath () {
|
||||
return resolve(app.getPath('userData'), './download.session')
|
||||
}
|
||||
|
||||
export function getUserDataPath () {
|
||||
return app.getPath('userData')
|
||||
}
|
||||
|
||||
export function getUserDownloadsPath () {
|
||||
export const getUserDownloadsPath = () => {
|
||||
return app.getPath('downloads')
|
||||
}
|
||||
|
||||
export function getEngineBin (platform) {
|
||||
let result = engineBinMap.hasOwnProperty(platform) ? engineBinMap[platform] : ''
|
||||
export const getConfigBasePath = () => {
|
||||
const path = getUserDataPath()
|
||||
return path
|
||||
}
|
||||
|
||||
export const getSessionPath = () => {
|
||||
return resolve(getUserDataPath(), './download.session')
|
||||
}
|
||||
|
||||
export const getEnginePidPath = () => {
|
||||
return resolve(getUserDataPath(), './engine.pid')
|
||||
}
|
||||
|
||||
export const getDhtPath = (protocol) => {
|
||||
const name = protocol === IP_VERSION.V6 ? 'dht6.dat' : 'dht.dat'
|
||||
return resolve(getUserDataPath(), `./${name}`)
|
||||
}
|
||||
|
||||
export const getEngineBin = (platform) => {
|
||||
const result = engineBinMap[platform] || ''
|
||||
return result
|
||||
}
|
||||
|
||||
export function transformConfig (config) {
|
||||
let result = []
|
||||
export const getEngineArch = (platform, arch) => {
|
||||
if (!['darwin', 'win32', 'linux'].includes(platform)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const result = engineArchMap[platform][arch]
|
||||
return result
|
||||
}
|
||||
|
||||
export const getDevEnginePath = (platform, arch) => {
|
||||
const ah = getEngineArch(platform, arch)
|
||||
const base = `../../../extra/${platform}/${ah}/engine`
|
||||
const result = resolve(__dirname, base)
|
||||
return result
|
||||
}
|
||||
|
||||
export const getProdEnginePath = () => {
|
||||
return resolve(app.getAppPath(), '../engine')
|
||||
}
|
||||
|
||||
export const getEnginePath = (platform, arch) => {
|
||||
return is.dev() ? getDevEnginePath(platform, arch) : getProdEnginePath()
|
||||
}
|
||||
|
||||
export const getAria2BinPath = (platform, arch) => {
|
||||
const base = getEnginePath(platform, arch)
|
||||
const binName = getEngineBin(platform)
|
||||
const result = resolve(base, `./${binName}`)
|
||||
return result
|
||||
}
|
||||
|
||||
export const getAria2ConfPath = (platform, arch) => {
|
||||
const base = getEnginePath(platform, arch)
|
||||
return resolve(base, './aria2.conf')
|
||||
}
|
||||
|
||||
export const transformConfig = (config) => {
|
||||
const result = []
|
||||
for (const [k, v] of Object.entries(config)) {
|
||||
if (v !== '') {
|
||||
result.push(`--${k}=${v}`)
|
||||
@@ -41,7 +94,7 @@ export function transformConfig (config) {
|
||||
return result
|
||||
}
|
||||
|
||||
export function isRunningInDmg () {
|
||||
export const isRunningInDmg = () => {
|
||||
if (!is.macOS() || is.dev()) {
|
||||
return false
|
||||
}
|
||||
@@ -50,7 +103,7 @@ export function isRunningInDmg () {
|
||||
return result
|
||||
}
|
||||
|
||||
export function moveAppToApplicationsFolder (errorMsg = '') {
|
||||
export const moveAppToApplicationsFolder = (errorMsg = '') => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = app.moveToApplicationsFolder()
|
||||
@@ -65,7 +118,7 @@ export function moveAppToApplicationsFolder (errorMsg = '') {
|
||||
})
|
||||
}
|
||||
|
||||
export function splitArgv (argv) {
|
||||
export const splitArgv = (argv) => {
|
||||
const args = []
|
||||
const extra = {}
|
||||
for (const arg of argv) {
|
||||
@@ -81,8 +134,8 @@ export function splitArgv (argv) {
|
||||
return { args, extra }
|
||||
}
|
||||
|
||||
export function parseArgvAsUrl (argv) {
|
||||
let arg = argv[1]
|
||||
export const parseArgvAsUrl = (argv) => {
|
||||
const arg = argv[1]
|
||||
if (!arg) {
|
||||
return
|
||||
}
|
||||
@@ -92,16 +145,16 @@ export function parseArgvAsUrl (argv) {
|
||||
}
|
||||
}
|
||||
|
||||
export function checkIsSupportedSchema (url = '') {
|
||||
export const checkIsSupportedSchema = (url = '') => {
|
||||
const str = url.toLowerCase()
|
||||
if (
|
||||
str.startsWith('mo:') ||
|
||||
str.startsWith('motrix:') ||
|
||||
str.startsWith('ftp:') ||
|
||||
str.startsWith('http:') ||
|
||||
str.startsWith('https:') ||
|
||||
str.startsWith('ftp:') ||
|
||||
str.startsWith('magnet:') ||
|
||||
str.startsWith('thunder:')
|
||||
str.startsWith('thunder:') ||
|
||||
str.startsWith('mo:') ||
|
||||
str.startsWith('motrix:')
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
@@ -109,11 +162,11 @@ export function checkIsSupportedSchema (url = '') {
|
||||
}
|
||||
}
|
||||
|
||||
export function isDirectory (path) {
|
||||
export const isDirectory = (path) => {
|
||||
return existsSync(path) && lstatSync(path).isDirectory()
|
||||
}
|
||||
|
||||
export function parseArgvAsFile (argv) {
|
||||
export const parseArgvAsFile = (argv) => {
|
||||
let arg = argv[1]
|
||||
if (!arg || isDirectory(arg)) {
|
||||
return
|
||||
@@ -124,3 +177,38 @@ export function parseArgvAsFile (argv) {
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
export const getMaxConnectionPerServer = () => {
|
||||
return ENGINE_MAX_CONNECTION_PER_SERVER
|
||||
}
|
||||
|
||||
export const getSystemTheme = () => {
|
||||
let result = APP_THEME.LIGHT
|
||||
result = nativeTheme.shouldUseDarkColors ? APP_THEME.DARK : APP_THEME.LIGHT
|
||||
return result
|
||||
}
|
||||
|
||||
export const convertArrayBufferToBuffer = (arrayBuffer) => {
|
||||
const buffer = Buffer.alloc(arrayBuffer.byteLength)
|
||||
const view = new Uint8Array(arrayBuffer)
|
||||
for (let i = 0; i < buffer.length; ++i) {
|
||||
buffer[i] = view[i]
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
export const showItemInFolder = (fullPath) => {
|
||||
if (!fullPath) {
|
||||
return
|
||||
}
|
||||
|
||||
fullPath = resolve(fullPath)
|
||||
access(fullPath, constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
logger.warn(`[Motrix] ${fullPath} ${err ? 'does not exist' : 'exists'}`)
|
||||
return
|
||||
}
|
||||
|
||||
shell.showItemInFolder(fullPath)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
export function concat (template, submenu, submenuToAdd) {
|
||||
import { parse } from 'querystring'
|
||||
|
||||
export const concat = (template, submenu, submenuToAdd) => {
|
||||
submenuToAdd.forEach(sub => {
|
||||
let relativeItem = null
|
||||
if (sub.position) {
|
||||
switch (sub.position) {
|
||||
case 'first':
|
||||
submenu.unshift(sub)
|
||||
break
|
||||
case 'last':
|
||||
submenu.push(sub)
|
||||
break
|
||||
case 'before':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
let array = relativeItem.__parent
|
||||
let index = array.indexOf(relativeItem)
|
||||
array.splice(index, 0, sub)
|
||||
}
|
||||
break
|
||||
case 'after':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
let array = relativeItem.__parent
|
||||
let index = array.indexOf(relativeItem)
|
||||
array.splice(index + 1, 0, sub)
|
||||
}
|
||||
break
|
||||
default:
|
||||
submenu.push(sub)
|
||||
break
|
||||
case 'first':
|
||||
submenu.unshift(sub)
|
||||
break
|
||||
case 'last':
|
||||
submenu.push(sub)
|
||||
break
|
||||
case 'before':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
const array = relativeItem.__parent
|
||||
const index = array.indexOf(relativeItem)
|
||||
array.splice(index, 0, sub)
|
||||
}
|
||||
break
|
||||
case 'after':
|
||||
relativeItem = findById(template, sub['relative-id'])
|
||||
if (relativeItem) {
|
||||
const array = relativeItem.__parent
|
||||
const index = array.indexOf(relativeItem)
|
||||
array.splice(index + 1, 0, sub)
|
||||
}
|
||||
break
|
||||
default:
|
||||
submenu.push(sub)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
submenu.push(sub)
|
||||
@@ -35,9 +37,9 @@ export function concat (template, submenu, submenuToAdd) {
|
||||
})
|
||||
}
|
||||
|
||||
export function merge (template, item) {
|
||||
export const merge = (template, item) => {
|
||||
if (item.id) {
|
||||
let matched = findById(template, item.id)
|
||||
const matched = findById(template, item.id)
|
||||
if (matched) {
|
||||
if (item.submenu && Array.isArray(item.submenu)) {
|
||||
if (!Array.isArray(matched.submenu)) {
|
||||
@@ -54,15 +56,15 @@ export function merge (template, item) {
|
||||
}
|
||||
|
||||
function findById (template, id) {
|
||||
for (let i in template) {
|
||||
let item = template[i]
|
||||
for (const i in template) {
|
||||
const item = template[i]
|
||||
if (item.id === id) {
|
||||
// Returned item need to have a reference to parent Array (.__parent).
|
||||
// This is required to handle `position` and `relative-id`
|
||||
item.__parent = template
|
||||
return item
|
||||
} else if (Array.isArray(item.submenu)) {
|
||||
let result = findById(item.submenu, id)
|
||||
const result = findById(item.submenu, id)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
@@ -71,9 +73,9 @@ function findById (template, id) {
|
||||
return null
|
||||
}
|
||||
|
||||
export function translateTemplate (template, keystrokesByCommand, i18n) {
|
||||
for (let i in template) {
|
||||
let item = template[i]
|
||||
export const translateTemplate = (template, keystrokesByCommand, i18n) => {
|
||||
for (const i in template) {
|
||||
const item = template[i]
|
||||
if (item.command) {
|
||||
item.accelerator = acceleratorForCommand(item.command, keystrokesByCommand)
|
||||
}
|
||||
@@ -99,7 +101,7 @@ export function translateTemplate (template, keystrokesByCommand, i18n) {
|
||||
return template
|
||||
}
|
||||
|
||||
export function handleCommand (item) {
|
||||
export const handleCommand = (item) => {
|
||||
handleCommandBefore(item)
|
||||
|
||||
const args = item['command-arg']
|
||||
@@ -112,30 +114,28 @@ export function handleCommand (item) {
|
||||
}
|
||||
|
||||
function handleCommandBefore (item) {
|
||||
console.log('handleCommandBefore==1=>', item)
|
||||
if (!item['command-before']) {
|
||||
return
|
||||
}
|
||||
const [ command, ...args ] = item['command-before'].split(',')
|
||||
console.log('handleCommandBefore==2=>', command, ...args)
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
const [command, params] = item['command-before'].split('?')
|
||||
const args = parse(params)
|
||||
global.application.sendCommandToAll(command, args)
|
||||
}
|
||||
|
||||
function handleCommandAfter (item) {
|
||||
console.log('handleCommandAfter==1=>', item)
|
||||
if (!item['command-after']) {
|
||||
return
|
||||
}
|
||||
const [ command, ...args ] = item['command-after'].split(',')
|
||||
console.log('handleCommandAfter==2=>', command, ...args)
|
||||
global.application.sendCommandToAll(command, ...args)
|
||||
const [command, params] = item['command-after'].split('?')
|
||||
const args = parse(params)
|
||||
global.application.sendCommandToAll(command, args)
|
||||
}
|
||||
|
||||
function acceleratorForCommand (command, keystrokesByCommand) {
|
||||
const keystroke = keystrokesByCommand[command]
|
||||
if (keystroke) {
|
||||
let modifiers = keystroke.split(/-(?=.)/)
|
||||
let key = modifiers.pop().toUpperCase()
|
||||
const key = modifiers.pop().toUpperCase()
|
||||
.replace('+', 'Plus')
|
||||
.replace('MINUS', '-')
|
||||
modifiers = modifiers.map((modifier) => {
|
||||
@@ -152,14 +152,14 @@ function acceleratorForCommand (command, keystrokesByCommand) {
|
||||
.replace(/alt/ig, 'Alt')
|
||||
}
|
||||
})
|
||||
let keys = modifiers.concat([key])
|
||||
const keys = modifiers.concat([key])
|
||||
return keys.join('+')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function flattenMenuItems (menu) {
|
||||
let flattenItems = {}
|
||||
export const flattenMenuItems = (menu) => {
|
||||
const flattenItems = {}
|
||||
menu.items.forEach(item => {
|
||||
if (item.id) {
|
||||
flattenItems[item.id] = item
|
||||
@@ -171,26 +171,26 @@ export function flattenMenuItems (menu) {
|
||||
return flattenItems
|
||||
}
|
||||
|
||||
export function updateStates (itemsById, visibleStates, enabledStates, checkedStates) {
|
||||
export const updateStates = (itemsById, visibleStates, enabledStates, checkedStates) => {
|
||||
if (visibleStates) {
|
||||
for (let command in visibleStates) {
|
||||
let item = itemsById[command]
|
||||
for (const command in visibleStates) {
|
||||
const item = itemsById[command]
|
||||
if (item) {
|
||||
item.visible = visibleStates[command]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (enabledStates) {
|
||||
for (let command in enabledStates) {
|
||||
let item = itemsById[command]
|
||||
for (const command in enabledStates) {
|
||||
const item = itemsById[command]
|
||||
if (item) {
|
||||
item.enabled = enabledStates[command]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checkedStates) {
|
||||
for (let id in checkedStates) {
|
||||
let item = itemsById[id]
|
||||
for (const id in checkedStates) {
|
||||
const item = itemsById[id]
|
||||
if (item) {
|
||||
item.checked = checkedStates[id]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { remote } from 'electron'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import is from 'electron-is'
|
||||
import { isEmpty } from 'lodash'
|
||||
import Aria2 from 'aria2'
|
||||
import { isEmpty, clone } from 'lodash'
|
||||
import { Aria2 } from '@shared/aria2'
|
||||
import {
|
||||
separateConfig,
|
||||
compactUndefined,
|
||||
@@ -10,24 +10,20 @@ import {
|
||||
changeKeysToCamelCase,
|
||||
changeKeysToKebabCase
|
||||
} from '@shared/utils'
|
||||
import {
|
||||
BEST_TRACKERS_URL,
|
||||
BEST_TRACKERS_IP_URL
|
||||
} from '@shared/constants'
|
||||
|
||||
const application = remote.getGlobal('application')
|
||||
import { ENGINE_RPC_HOST } from '@shared/constants'
|
||||
|
||||
export default class Api {
|
||||
constructor (options = {}) {
|
||||
this.options = options
|
||||
|
||||
this.client = null
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
this.loadConfig()
|
||||
this.initClient()
|
||||
async init () {
|
||||
this.config = await this.loadConfig()
|
||||
|
||||
this.client = this.initClient()
|
||||
this.client.open()
|
||||
}
|
||||
|
||||
loadConfigFromLocalStorage () {
|
||||
@@ -36,21 +32,18 @@ export default class Api {
|
||||
return result
|
||||
}
|
||||
|
||||
loadConfigFromNativeStore () {
|
||||
const systemConfig = application.configManager.getSystemConfig()
|
||||
const userConfig = application.configManager.getUserConfig()
|
||||
|
||||
const result = { ...systemConfig, ...userConfig }
|
||||
async loadConfigFromNativeStore () {
|
||||
const result = await ipcRenderer.invoke('get-app-config')
|
||||
return result
|
||||
}
|
||||
|
||||
loadConfig () {
|
||||
async loadConfig () {
|
||||
let result = is.renderer()
|
||||
? this.loadConfigFromNativeStore()
|
||||
? await this.loadConfigFromNativeStore()
|
||||
: this.loadConfigFromLocalStorage()
|
||||
|
||||
result = changeKeysToCamelCase(result)
|
||||
this.config = result
|
||||
return result
|
||||
}
|
||||
|
||||
initClient () {
|
||||
@@ -58,13 +51,12 @@ export default class Api {
|
||||
rpcListenPort: port,
|
||||
rpcSecret: secret
|
||||
} = this.config
|
||||
const host = '127.0.0.1'
|
||||
this.client = new Aria2({
|
||||
const host = ENGINE_RPC_HOST
|
||||
return new Aria2({
|
||||
host,
|
||||
port,
|
||||
secret
|
||||
})
|
||||
this.client.open()
|
||||
}
|
||||
|
||||
closeClient () {
|
||||
@@ -79,7 +71,7 @@ export default class Api {
|
||||
|
||||
fetchPreference () {
|
||||
return new Promise((resolve) => {
|
||||
this.loadConfig()
|
||||
this.config = this.loadConfig()
|
||||
resolve(this.config)
|
||||
})
|
||||
}
|
||||
@@ -99,20 +91,24 @@ export default class Api {
|
||||
|
||||
savePreferenceToNativeStore (params = {}) {
|
||||
const { user, system, others } = separateConfig(params)
|
||||
if (!isEmpty(system)) {
|
||||
console.info('[Motrix] save system config: ', system)
|
||||
application.configManager.setSystemConfig(system)
|
||||
this.changeGlobalOption(system)
|
||||
}
|
||||
const config = {}
|
||||
|
||||
if (!isEmpty(user)) {
|
||||
console.info('[Motrix] save user config: ', user)
|
||||
application.configManager.setUserConfig(user)
|
||||
config.user = user
|
||||
}
|
||||
|
||||
if (!isEmpty(system)) {
|
||||
console.info('[Motrix] save system config: ', system)
|
||||
config.system = system
|
||||
this.updateActiveTaskOption(system)
|
||||
}
|
||||
|
||||
if (!isEmpty(others)) {
|
||||
console.info('[Motrix] save config found iillegal key: ', others)
|
||||
console.info('[Motrix] save config found illegal key: ', others)
|
||||
}
|
||||
|
||||
ipcRenderer.send('command', 'application:save-preference', config)
|
||||
}
|
||||
|
||||
getVersion () {
|
||||
@@ -146,12 +142,23 @@ export default class Api {
|
||||
})
|
||||
}
|
||||
|
||||
changeOption (params = {}) {
|
||||
let { gid, options = {} } = params
|
||||
options = formatOptionsForEngine(options)
|
||||
updateActiveTaskOption (options) {
|
||||
this.fetchTaskList({ type: 'active' })
|
||||
.then((data) => {
|
||||
if (isEmpty(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([gid, kebabOptions])
|
||||
const gids = data.map((task) => task.gid)
|
||||
this.batchChangeOption({ gids, options })
|
||||
})
|
||||
}
|
||||
|
||||
changeOption (params = {}) {
|
||||
const { gid, options = {} } = params
|
||||
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
const args = compactUndefined([gid, engineOptions])
|
||||
|
||||
return this.client.call('changeOption', ...args)
|
||||
}
|
||||
@@ -163,12 +170,16 @@ export default class Api {
|
||||
addUri (params) {
|
||||
const {
|
||||
uris,
|
||||
outs,
|
||||
options
|
||||
} = params
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const tasks = uris.map((uri) => {
|
||||
const args = compactUndefined([[uri], kebabOptions])
|
||||
return [ 'aria2.addUri', ...args ]
|
||||
const tasks = uris.map((uri, index) => {
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
if (outs && outs[index]) {
|
||||
engineOptions.out = outs[index]
|
||||
}
|
||||
const args = compactUndefined([[uri], engineOptions])
|
||||
return ['aria2.addUri', ...args]
|
||||
})
|
||||
return this.client.multicall(tasks)
|
||||
}
|
||||
@@ -178,8 +189,8 @@ export default class Api {
|
||||
torrent,
|
||||
options
|
||||
} = params
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([torrent, [], kebabOptions])
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
const args = compactUndefined([torrent, [], engineOptions])
|
||||
return this.client.call('addTorrent', ...args)
|
||||
}
|
||||
|
||||
@@ -188,8 +199,8 @@ export default class Api {
|
||||
metalink,
|
||||
options
|
||||
} = params
|
||||
const kebabOptions = changeKeysToKebabCase(options)
|
||||
const args = compactUndefined([metalink, kebabOptions])
|
||||
const engineOptions = formatOptionsForEngine(options)
|
||||
const args = compactUndefined([metalink, engineOptions])
|
||||
return this.client.call('addMetalink', ...args)
|
||||
}
|
||||
|
||||
@@ -199,14 +210,14 @@ export default class Api {
|
||||
const waitingArgs = compactUndefined([offset, num, keys])
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.multicall([
|
||||
[ 'aria2.tellActive', ...activeArgs ],
|
||||
[ 'aria2.tellWaiting', ...waitingArgs ]
|
||||
['aria2.tellActive', ...activeArgs],
|
||||
['aria2.tellWaiting', ...waitingArgs]
|
||||
]).then((data) => {
|
||||
console.log('fetchDownloadingTaskList data', data)
|
||||
console.log('[Motrix] fetch downloading task list data:', data)
|
||||
const result = mergeTaskResult(data)
|
||||
resolve(result)
|
||||
}).catch((err) => {
|
||||
console.log('fetchDownloadingTaskList fail===>', err)
|
||||
console.log('[Motrix] fetch downloading task list fail:', err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
@@ -224,17 +235,23 @@ export default class Api {
|
||||
return this.client.call('tellStopped', ...args)
|
||||
}
|
||||
|
||||
fetchActiveTaskList (params = {}) {
|
||||
const { keys } = params
|
||||
const args = compactUndefined([keys])
|
||||
return this.client.call('tellActive', ...args)
|
||||
}
|
||||
|
||||
fetchTaskList (params = {}) {
|
||||
const { type } = params
|
||||
switch (type) {
|
||||
case 'active':
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
case 'waiting':
|
||||
return this.fetchWaitingTaskList(params)
|
||||
case 'stopped':
|
||||
return this.fetchStoppedTaskList(params)
|
||||
default:
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
case 'active':
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
case 'waiting':
|
||||
return this.fetchWaitingTaskList(params)
|
||||
case 'stopped':
|
||||
return this.fetchStoppedTaskList(params)
|
||||
default:
|
||||
return this.fetchDownloadingTaskList(params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +261,36 @@ export default class Api {
|
||||
return this.client.call('tellStatus', ...args)
|
||||
}
|
||||
|
||||
fetchTaskItemWithPeers (params = {}) {
|
||||
const { gid, keys } = params
|
||||
const statusArgs = compactUndefined([gid, keys])
|
||||
const peersArgs = compactUndefined([gid])
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.multicall([
|
||||
['aria2.tellStatus', ...statusArgs],
|
||||
['aria2.getPeers', ...peersArgs]
|
||||
]).then((data) => {
|
||||
console.log('[Motrix] fetchTaskItemWithPeers:', data)
|
||||
const result = data[0] && data[0][0]
|
||||
const peers = data[1] && data[1][0]
|
||||
result.peers = peers || []
|
||||
console.log('[Motrix] fetchTaskItemWithPeers.result:', result)
|
||||
console.log('[Motrix] fetchTaskItemWithPeers.peers:', peers)
|
||||
|
||||
resolve(result)
|
||||
}).catch((err) => {
|
||||
console.log('[Motrix] fetch downloading task list fail:', err)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetchTaskItemPeers (params = {}) {
|
||||
const { gid, keys } = params
|
||||
const args = compactUndefined([gid, keys])
|
||||
return this.client.call('getPeers', ...args)
|
||||
}
|
||||
|
||||
pauseTask (params = {}) {
|
||||
const { gid } = params
|
||||
const args = compactUndefined([gid])
|
||||
@@ -300,24 +347,35 @@ export default class Api {
|
||||
return this.client.call('removeDownloadResult', ...args)
|
||||
}
|
||||
|
||||
startPowerSaveBlocker () {
|
||||
application.energyManager.startPowerSaveBlocker()
|
||||
}
|
||||
multicall (method, params = {}) {
|
||||
let { gids, options = {} } = params
|
||||
options = formatOptionsForEngine(options)
|
||||
|
||||
stopPowerSaveBlocker () {
|
||||
application.energyManager.stopPowerSaveBlocker()
|
||||
}
|
||||
|
||||
fetchBtTrackerFromGitHub () {
|
||||
const now = Date.now()
|
||||
const promises = [
|
||||
fetch(`${BEST_TRACKERS_IP_URL}?t=${now}`).then((res) => res.text()),
|
||||
fetch(`${BEST_TRACKERS_URL}?t=${now}`).then((res) => res.text())
|
||||
]
|
||||
|
||||
return Promise.all(promises).then((values) => {
|
||||
let result = values.join('\r\n').replace(/^\s*[\r\n]/gm, '')
|
||||
return result
|
||||
const data = gids.map((gid, index) => {
|
||||
const _options = clone(options)
|
||||
const args = compactUndefined([gid, _options])
|
||||
return [method, ...args]
|
||||
})
|
||||
return this.client.multicall(data)
|
||||
}
|
||||
|
||||
batchChangeOption (params = {}) {
|
||||
return this.multicall('aria2.changeOption', params)
|
||||
}
|
||||
|
||||
batchRemoveTask (params = {}) {
|
||||
return this.multicall('aria2.remove', params)
|
||||
}
|
||||
|
||||
batchResumeTask (params = {}) {
|
||||
return this.multicall('aria2.unpause', params)
|
||||
}
|
||||
|
||||
batchPauseTask (params = {}) {
|
||||
return this.multicall('aria2.pause', params)
|
||||
}
|
||||
|
||||
batchForcePauseTask (params = {}) {
|
||||
return this.multicall('aria2.forcePause', params)
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g class="nc-icon-wrapper" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<line data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" x1="12" y1="2" x2="12" y2="22"/>
|
||||
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="19,15 12,22 5,15 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 429 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<g class="nc-icon-wrapper" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" stroke="currentColor">
|
||||
<line data-cap="butt" data-color="color-2" fill="none" stroke-miterlimit="10" x1="12" y1="22" x2="12" y2="2"/>
|
||||
<polyline fill="none" stroke="currentColor" stroke-miterlimit="10" points="5,9 12,2 19,9 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 426 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g stroke-linecap="square" stroke-linejoin="miter" stroke-width="2" fill="#000000" stroke="#000000">
|
||||
<line fill="none" stroke-miterlimit="10" x1="8.2" y1="4.5" x2="11.1" y2="7.3"></line>
|
||||
<line fill="none" stroke-miterlimit="10" x1="16.7" y1="12.9" x2="19.5" y2="15.8"></line>
|
||||
<path fill="none" stroke="#000000" stroke-miterlimit="10"
|
||||
d="M12.5,17.2 c-1.6,1.6-4.1,1.6-5.7,0c-1.6-1.6-1.6-4.1,0-5.7l7.1-7.1l-2.8-2.8L4,8.7C0.9,11.8,0.9,16.9,4,20s8.2,3.1,11.3,0l7.1-7.1l-2.8-2.8 L12.5,17.2z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 617 B |