chore: 项目文件结构重构
删除多个不再使用的源文件,包括动画、下载、配置、UI 相关文件及图标,清理代码库以提高可维护性。
979
result.csv
Normal file
@@ -0,0 +1,979 @@
|
|||||||
|
IP 地址,已发送,已接收,丢包率,平均延迟,下载速度(MB/s),地区码
|
||||||
|
141.101.121.206,4,4,0.00,170.61,0.00,N/A
|
||||||
|
141.101.120.141,4,4,0.00,172.19,0.00,N/A
|
||||||
|
172.64.34.217,4,4,0.00,172.73,0.00,N/A
|
||||||
|
104.18.232.108,4,4,0.00,175.13,0.00,N/A
|
||||||
|
104.19.49.47,4,4,0.00,175.76,0.00,N/A
|
||||||
|
104.18.210.110,4,4,0.00,176.37,0.00,N/A
|
||||||
|
104.18.133.38,4,4,0.00,176.87,0.00,N/A
|
||||||
|
172.65.200.235,4,4,0.00,180.18,0.00,N/A
|
||||||
|
172.65.206.215,4,4,0.00,181.09,0.00,N/A
|
||||||
|
172.65.88.110,4,4,0.00,181.92,0.00,N/A
|
||||||
|
104.18.87.58,4,4,0.00,182.15,0.00,N/A
|
||||||
|
104.27.195.221,4,4,0.00,182.32,0.00,N/A
|
||||||
|
172.64.35.209,4,4,0.00,182.80,0.00,N/A
|
||||||
|
172.65.83.97,4,4,0.00,183.80,0.00,N/A
|
||||||
|
172.67.115.45,4,4,0.00,184.35,0.00,N/A
|
||||||
|
172.67.109.61,4,4,0.00,184.42,0.00,N/A
|
||||||
|
104.27.199.104,4,4,0.00,184.61,0.00,N/A
|
||||||
|
104.27.55.140,4,4,0.00,184.95,0.00,N/A
|
||||||
|
104.23.126.50,4,4,0.00,186.35,0.00,N/A
|
||||||
|
104.23.97.94,4,4,0.00,186.58,0.00,N/A
|
||||||
|
172.65.80.97,4,4,0.00,187.11,0.00,N/A
|
||||||
|
172.65.141.175,4,4,0.00,187.34,0.00,N/A
|
||||||
|
104.27.49.243,4,4,0.00,188.22,0.00,N/A
|
||||||
|
108.162.195.23,4,4,0.00,189.54,0.00,N/A
|
||||||
|
172.65.146.228,4,4,0.00,189.77,0.00,N/A
|
||||||
|
172.65.89.157,4,4,0.00,189.96,0.00,N/A
|
||||||
|
104.25.4.81,4,4,0.00,190.07,0.00,N/A
|
||||||
|
104.25.11.89,4,4,0.00,190.35,0.00,N/A
|
||||||
|
172.65.90.242,4,4,0.00,190.36,0.00,N/A
|
||||||
|
104.25.138.144,4,4,0.00,190.39,0.00,N/A
|
||||||
|
104.27.200.172,4,4,0.00,190.83,0.00,N/A
|
||||||
|
172.67.88.239,4,4,0.00,191.16,0.00,N/A
|
||||||
|
104.18.250.196,4,4,0.00,192.35,0.00,N/A
|
||||||
|
172.65.240.126,4,4,0.00,192.80,0.00,N/A
|
||||||
|
104.27.118.3,4,4,0.00,192.95,0.00,N/A
|
||||||
|
104.25.63.97,4,4,0.00,192.96,0.00,N/A
|
||||||
|
104.27.204.236,4,4,0.00,193.05,0.00,N/A
|
||||||
|
104.25.47.196,4,4,0.00,193.90,0.00,N/A
|
||||||
|
172.66.135.31,4,4,0.00,194.02,0.00,N/A
|
||||||
|
104.16.201.188,4,4,0.00,194.24,0.00,N/A
|
||||||
|
104.27.201.28,4,4,0.00,195.17,0.00,N/A
|
||||||
|
104.27.66.223,4,4,0.00,195.48,0.00,N/A
|
||||||
|
172.65.81.92,4,4,0.00,196.15,0.00,N/A
|
||||||
|
172.65.138.75,4,4,0.00,196.45,0.00,N/A
|
||||||
|
162.159.9.55,4,4,0.00,196.60,0.00,N/A
|
||||||
|
104.19.77.26,4,4,0.00,197.43,0.00,N/A
|
||||||
|
172.65.125.141,4,4,0.00,197.70,0.00,N/A
|
||||||
|
104.25.101.238,4,4,0.00,199.28,0.00,N/A
|
||||||
|
141.101.113.135,4,4,0.00,199.94,0.00,N/A
|
||||||
|
104.24.253.165,4,4,0.00,201.58,0.00,N/A
|
||||||
|
104.25.43.12,4,4,0.00,201.72,0.00,N/A
|
||||||
|
104.27.64.178,4,4,0.00,202.07,0.00,N/A
|
||||||
|
104.25.40.112,4,4,0.00,202.49,0.00,N/A
|
||||||
|
172.65.85.42,4,4,0.00,203.00,0.00,N/A
|
||||||
|
104.27.206.177,4,4,0.00,203.81,0.00,N/A
|
||||||
|
104.27.194.185,4,4,0.00,204.52,0.00,N/A
|
||||||
|
104.27.57.176,4,4,0.00,204.72,0.00,N/A
|
||||||
|
162.159.6.99,4,4,0.00,205.14,0.00,N/A
|
||||||
|
104.16.208.207,4,4,0.00,205.96,0.00,N/A
|
||||||
|
172.65.106.7,4,4,0.00,206.00,0.00,N/A
|
||||||
|
104.25.100.152,4,4,0.00,206.14,0.00,N/A
|
||||||
|
104.25.9.10,4,4,0.00,206.22,0.00,N/A
|
||||||
|
104.25.2.103,4,4,0.00,206.67,0.00,N/A
|
||||||
|
104.25.6.214,4,4,0.00,207.38,0.00,N/A
|
||||||
|
104.27.125.248,4,4,0.00,207.56,0.00,N/A
|
||||||
|
104.23.99.171,4,4,0.00,208.38,0.00,N/A
|
||||||
|
162.159.20.67,4,4,0.00,208.69,0.00,N/A
|
||||||
|
172.65.137.158,4,4,0.00,208.89,0.00,N/A
|
||||||
|
141.101.90.252,4,4,0.00,208.91,0.00,N/A
|
||||||
|
104.17.27.5,4,4,0.00,209.43,0.00,N/A
|
||||||
|
104.23.96.103,4,4,0.00,209.71,0.00,N/A
|
||||||
|
198.41.200.113,4,4,0.00,210.96,0.00,N/A
|
||||||
|
104.25.3.135,4,4,0.00,211.53,0.00,N/A
|
||||||
|
172.67.192.153,4,4,0.00,213.75,0.00,N/A
|
||||||
|
172.67.128.121,4,4,0.00,216.03,0.00,N/A
|
||||||
|
104.16.192.233,4,4,0.00,216.10,0.00,N/A
|
||||||
|
104.29.158.150,4,4,0.00,216.16,0.00,N/A
|
||||||
|
104.19.27.221,4,4,0.00,217.16,0.00,N/A
|
||||||
|
104.16.238.110,4,4,0.00,219.89,0.00,N/A
|
||||||
|
104.21.110.121,4,4,0.00,220.02,0.00,N/A
|
||||||
|
172.67.142.189,4,4,0.00,221.18,0.00,N/A
|
||||||
|
162.159.10.98,4,4,0.00,221.72,0.00,N/A
|
||||||
|
172.67.141.146,4,4,0.00,222.05,0.00,N/A
|
||||||
|
104.21.21.9,4,4,0.00,222.20,0.00,N/A
|
||||||
|
104.29.157.254,4,4,0.00,223.36,0.00,N/A
|
||||||
|
104.21.44.171,4,4,0.00,223.49,0.00,N/A
|
||||||
|
104.21.33.67,4,4,0.00,224.51,0.00,N/A
|
||||||
|
162.159.14.250,4,4,0.00,225.19,0.00,N/A
|
||||||
|
172.67.149.124,4,4,0.00,225.46,0.00,N/A
|
||||||
|
104.21.60.76,4,4,0.00,226.39,0.00,N/A
|
||||||
|
104.21.34.167,4,4,0.00,228.03,0.00,N/A
|
||||||
|
104.21.66.44,4,4,0.00,228.14,0.00,N/A
|
||||||
|
198.41.193.202,4,4,0.00,228.58,0.00,N/A
|
||||||
|
172.67.231.90,4,4,0.00,229.40,0.00,N/A
|
||||||
|
172.65.60.144,4,4,0.00,229.46,0.00,N/A
|
||||||
|
162.159.11.102,4,4,0.00,229.99,0.00,N/A
|
||||||
|
188.114.97.221,4,4,0.00,230.32,0.00,N/A
|
||||||
|
104.21.31.187,4,4,0.00,230.39,0.00,N/A
|
||||||
|
198.41.206.251,4,4,0.00,230.57,0.00,N/A
|
||||||
|
172.67.169.129,4,4,0.00,231.16,0.00,N/A
|
||||||
|
104.18.221.160,4,4,0.00,231.31,0.00,N/A
|
||||||
|
172.67.251.106,4,4,0.00,231.38,0.00,N/A
|
||||||
|
104.21.19.88,4,4,0.00,232.40,0.00,N/A
|
||||||
|
104.21.76.1,4,4,0.00,233.29,0.00,N/A
|
||||||
|
104.21.85.131,4,4,0.00,233.30,0.00,N/A
|
||||||
|
104.21.25.159,4,4,0.00,233.59,0.00,N/A
|
||||||
|
172.67.208.10,4,4,0.00,234.32,0.00,N/A
|
||||||
|
172.64.92.242,4,4,0.00,234.75,0.00,N/A
|
||||||
|
172.65.5.190,4,4,0.00,235.78,0.00,N/A
|
||||||
|
173.245.59.112,4,4,0.00,237.44,0.00,N/A
|
||||||
|
104.21.108.113,4,4,0.00,237.57,0.00,N/A
|
||||||
|
104.18.245.3,4,4,0.00,238.08,0.00,N/A
|
||||||
|
162.159.40.247,4,4,0.00,239.48,0.00,N/A
|
||||||
|
104.19.0.13,4,4,0.00,240.12,0.00,N/A
|
||||||
|
104.21.42.59,4,4,0.00,240.75,0.00,N/A
|
||||||
|
104.19.15.109,4,4,0.00,248.82,0.00,N/A
|
||||||
|
104.21.49.195,4,4,0.00,249.41,0.00,N/A
|
||||||
|
104.19.14.123,4,4,0.00,250.93,0.00,N/A
|
||||||
|
198.41.199.9,4,4,0.00,253.58,0.00,N/A
|
||||||
|
104.29.159.206,4,4,0.00,255.12,0.00,N/A
|
||||||
|
108.162.198.59,4,4,0.00,260.18,0.00,N/A
|
||||||
|
104.29.144.155,4,4,0.00,261.55,0.00,N/A
|
||||||
|
198.41.216.166,4,4,0.00,263.88,0.00,N/A
|
||||||
|
162.159.25.79,4,4,0.00,265.49,0.00,N/A
|
||||||
|
104.16.173.251,4,4,0.00,265.54,0.00,N/A
|
||||||
|
104.16.219.16,4,4,0.00,267.19,0.00,N/A
|
||||||
|
103.21.244.66,4,4,0.00,267.32,0.00,N/A
|
||||||
|
198.41.192.140,4,4,0.00,270.43,0.00,N/A
|
||||||
|
162.159.16.8,4,4,0.00,281.10,0.00,N/A
|
||||||
|
104.29.145.249,4,4,0.00,284.12,0.00,N/A
|
||||||
|
104.17.14.107,4,4,0.00,284.74,0.00,N/A
|
||||||
|
162.159.33.9,4,3,0.25,163.50,0.00,N/A
|
||||||
|
104.19.1.203,4,3,0.25,165.66,0.00,N/A
|
||||||
|
104.17.10.246,4,3,0.25,168.12,0.00,N/A
|
||||||
|
172.65.204.110,4,3,0.25,168.44,0.00,N/A
|
||||||
|
104.16.251.149,4,3,0.25,169.01,0.00,N/A
|
||||||
|
104.19.24.233,4,3,0.25,169.23,0.00,N/A
|
||||||
|
141.101.122.200,4,3,0.25,169.24,0.00,N/A
|
||||||
|
104.16.222.196,4,3,0.25,169.37,0.00,N/A
|
||||||
|
104.17.18.41,4,3,0.25,169.59,0.00,N/A
|
||||||
|
104.19.59.153,4,3,0.25,170.84,0.00,N/A
|
||||||
|
104.17.3.191,4,3,0.25,171.07,0.00,N/A
|
||||||
|
172.65.212.42,4,3,0.25,171.19,0.00,N/A
|
||||||
|
172.65.194.74,4,3,0.25,171.33,0.00,N/A
|
||||||
|
104.16.194.52,4,3,0.25,171.42,0.00,N/A
|
||||||
|
198.41.203.196,4,3,0.25,173.66,0.00,N/A
|
||||||
|
104.19.40.143,4,3,0.25,173.79,0.00,N/A
|
||||||
|
104.16.224.56,4,3,0.25,174.55,0.00,N/A
|
||||||
|
104.16.171.114,4,3,0.25,174.63,0.00,N/A
|
||||||
|
104.17.16.183,4,3,0.25,174.76,0.00,N/A
|
||||||
|
141.101.115.123,4,3,0.25,175.35,0.00,N/A
|
||||||
|
198.41.217.76,4,3,0.25,175.82,0.00,N/A
|
||||||
|
141.101.123.213,4,3,0.25,176.23,0.00,N/A
|
||||||
|
198.41.201.13,4,3,0.25,176.44,0.00,N/A
|
||||||
|
104.19.136.126,4,3,0.25,176.96,0.00,N/A
|
||||||
|
104.16.193.195,4,3,0.25,177.00,0.00,N/A
|
||||||
|
104.21.224.85,4,3,0.25,177.85,0.00,N/A
|
||||||
|
172.65.238.127,4,3,0.25,177.96,0.00,N/A
|
||||||
|
104.18.206.194,4,3,0.25,178.00,0.00,N/A
|
||||||
|
172.65.233.45,4,3,0.25,178.90,0.00,N/A
|
||||||
|
104.19.50.237,4,3,0.25,179.04,0.00,N/A
|
||||||
|
104.19.61.11,4,3,0.25,179.38,0.00,N/A
|
||||||
|
172.65.216.254,4,3,0.25,179.63,0.00,N/A
|
||||||
|
173.245.58.254,4,3,0.25,179.74,0.00,N/A
|
||||||
|
198.41.222.188,4,3,0.25,179.95,0.00,N/A
|
||||||
|
173.245.49.150,4,3,0.25,180.27,0.00,N/A
|
||||||
|
104.16.195.175,4,3,0.25,180.62,0.00,N/A
|
||||||
|
104.19.57.218,4,3,0.25,182.05,0.00,N/A
|
||||||
|
190.93.247.8,4,3,0.25,182.22,0.00,N/A
|
||||||
|
104.16.191.64,4,3,0.25,183.41,0.00,N/A
|
||||||
|
172.64.42.188,4,3,0.25,183.54,0.00,N/A
|
||||||
|
104.25.158.236,4,3,0.25,183.65,0.00,N/A
|
||||||
|
162.159.23.138,4,3,0.25,183.81,0.00,N/A
|
||||||
|
172.64.159.95,4,3,0.25,184.51,0.00,N/A
|
||||||
|
104.27.115.83,4,3,0.25,185.01,0.00,N/A
|
||||||
|
104.27.93.122,4,3,0.25,185.08,0.00,N/A
|
||||||
|
162.159.27.175,4,3,0.25,185.26,0.00,N/A
|
||||||
|
104.27.90.5,4,3,0.25,185.65,0.00,N/A
|
||||||
|
104.25.134.158,4,3,0.25,185.71,0.00,N/A
|
||||||
|
172.65.197.229,4,3,0.25,185.75,0.00,N/A
|
||||||
|
104.25.104.84,4,3,0.25,185.96,0.00,N/A
|
||||||
|
104.27.192.21,4,3,0.25,186.04,0.00,N/A
|
||||||
|
104.16.216.68,4,3,0.25,186.26,0.00,N/A
|
||||||
|
198.41.207.148,4,3,0.25,186.59,0.00,N/A
|
||||||
|
104.27.198.29,4,3,0.25,186.64,0.00,N/A
|
||||||
|
104.27.117.34,4,3,0.25,186.68,0.00,N/A
|
||||||
|
172.65.148.94,4,3,0.25,186.96,0.00,N/A
|
||||||
|
104.25.121.4,4,3,0.25,187.30,0.00,N/A
|
||||||
|
104.25.49.216,4,3,0.25,187.42,0.00,N/A
|
||||||
|
104.25.84.187,4,3,0.25,187.48,0.00,N/A
|
||||||
|
104.27.116.178,4,3,0.25,188.87,0.00,N/A
|
||||||
|
104.16.215.44,4,3,0.25,188.89,0.00,N/A
|
||||||
|
104.18.234.235,4,3,0.25,188.90,0.00,N/A
|
||||||
|
104.25.108.235,4,3,0.25,188.91,0.00,N/A
|
||||||
|
104.27.197.249,4,3,0.25,189.45,0.00,N/A
|
||||||
|
104.27.122.29,4,3,0.25,189.92,0.00,N/A
|
||||||
|
104.16.189.44,4,3,0.25,190.13,0.00,N/A
|
||||||
|
104.19.58.126,4,3,0.25,190.13,0.00,N/A
|
||||||
|
104.18.238.3,4,3,0.25,190.26,0.00,N/A
|
||||||
|
172.65.128.134,4,3,0.25,190.66,0.00,N/A
|
||||||
|
104.27.51.248,4,3,0.25,190.79,0.00,N/A
|
||||||
|
104.16.178.48,4,3,0.25,191.00,0.00,N/A
|
||||||
|
172.65.203.83,4,3,0.25,191.08,0.00,N/A
|
||||||
|
104.25.112.128,4,3,0.25,191.28,0.00,N/A
|
||||||
|
104.25.83.3,4,3,0.25,191.51,0.00,N/A
|
||||||
|
172.64.82.38,4,3,0.25,191.55,0.00,N/A
|
||||||
|
104.27.193.89,4,3,0.25,191.69,0.00,N/A
|
||||||
|
104.25.5.141,4,3,0.25,192.19,0.00,N/A
|
||||||
|
162.159.4.237,4,3,0.25,192.21,0.00,N/A
|
||||||
|
104.25.45.112,4,3,0.25,192.49,0.00,N/A
|
||||||
|
104.16.184.4,4,3,0.25,192.59,0.00,N/A
|
||||||
|
172.65.103.133,4,3,0.25,193.07,0.00,N/A
|
||||||
|
104.25.136.115,4,3,0.25,193.45,0.00,N/A
|
||||||
|
104.25.135.203,4,3,0.25,193.77,0.00,N/A
|
||||||
|
104.27.62.142,4,3,0.25,194.12,0.00,N/A
|
||||||
|
104.27.120.219,4,3,0.25,194.18,0.00,N/A
|
||||||
|
104.19.55.100,4,3,0.25,194.19,0.00,N/A
|
||||||
|
104.20.62.129,4,3,0.25,194.26,0.00,N/A
|
||||||
|
104.24.249.90,4,3,0.25,194.30,0.00,N/A
|
||||||
|
104.25.24.7,4,3,0.25,194.43,0.00,N/A
|
||||||
|
104.25.198.124,4,3,0.25,194.78,0.00,N/A
|
||||||
|
104.16.170.74,4,3,0.25,195.30,0.00,N/A
|
||||||
|
104.25.139.149,4,3,0.25,195.68,0.00,N/A
|
||||||
|
104.27.47.127,4,3,0.25,196.12,0.00,N/A
|
||||||
|
104.25.80.93,4,3,0.25,196.20,0.00,N/A
|
||||||
|
104.25.25.78,4,3,0.25,196.23,0.00,N/A
|
||||||
|
104.20.24.111,4,3,0.25,196.53,0.00,N/A
|
||||||
|
104.27.96.63,4,3,0.25,196.87,0.00,N/A
|
||||||
|
104.25.64.161,4,3,0.25,197.45,0.00,N/A
|
||||||
|
172.65.108.167,4,3,0.25,198.03,0.00,N/A
|
||||||
|
172.65.198.92,4,3,0.25,198.90,0.00,N/A
|
||||||
|
104.27.203.151,4,3,0.25,199.26,0.00,N/A
|
||||||
|
172.65.207.20,4,3,0.25,199.30,0.00,N/A
|
||||||
|
172.65.220.142,4,3,0.25,199.39,0.00,N/A
|
||||||
|
104.25.131.218,4,3,0.25,199.47,0.00,N/A
|
||||||
|
172.65.182.19,4,3,0.25,200.05,0.00,N/A
|
||||||
|
104.19.42.3,4,3,0.25,200.06,0.00,N/A
|
||||||
|
104.25.22.250,4,3,0.25,200.09,0.00,N/A
|
||||||
|
104.25.155.139,4,3,0.25,200.19,0.00,N/A
|
||||||
|
104.25.7.77,4,3,0.25,200.55,0.00,N/A
|
||||||
|
104.18.240.134,4,3,0.25,200.85,0.00,N/A
|
||||||
|
104.23.138.193,4,3,0.25,201.23,0.00,N/A
|
||||||
|
104.16.255.166,4,3,0.25,201.57,0.00,N/A
|
||||||
|
172.65.171.208,4,3,0.25,201.79,0.00,N/A
|
||||||
|
104.19.51.187,4,3,0.25,201.87,0.00,N/A
|
||||||
|
104.25.67.102,4,3,0.25,201.96,0.00,N/A
|
||||||
|
172.64.37.142,4,3,0.25,202.65,0.00,N/A
|
||||||
|
104.25.15.172,4,3,0.25,202.72,0.00,N/A
|
||||||
|
104.16.183.157,4,3,0.25,202.80,0.00,N/A
|
||||||
|
104.25.16.44,4,3,0.25,202.92,0.00,N/A
|
||||||
|
104.27.119.20,4,3,0.25,203.03,0.00,N/A
|
||||||
|
104.27.202.72,4,3,0.25,203.32,0.00,N/A
|
||||||
|
104.27.205.247,4,3,0.25,204.24,0.00,N/A
|
||||||
|
104.27.44.147,4,3,0.25,204.95,0.00,N/A
|
||||||
|
172.65.162.180,4,3,0.25,205.18,0.00,N/A
|
||||||
|
104.25.27.102,4,3,0.25,205.50,0.00,N/A
|
||||||
|
104.25.20.71,4,3,0.25,205.55,0.00,N/A
|
||||||
|
104.25.118.22,4,3,0.25,205.63,0.00,N/A
|
||||||
|
104.27.196.211,4,3,0.25,205.69,0.00,N/A
|
||||||
|
162.159.2.235,4,3,0.25,205.82,0.00,N/A
|
||||||
|
104.24.242.122,4,3,0.25,205.83,0.00,N/A
|
||||||
|
172.65.169.247,4,3,0.25,205.93,0.00,N/A
|
||||||
|
104.25.137.129,4,3,0.25,206.03,0.00,N/A
|
||||||
|
108.162.192.52,4,3,0.25,206.74,0.00,N/A
|
||||||
|
172.65.87.122,4,3,0.25,206.76,0.00,N/A
|
||||||
|
172.65.102.200,4,3,0.25,206.82,0.00,N/A
|
||||||
|
104.25.77.56,4,3,0.25,206.91,0.00,N/A
|
||||||
|
104.25.160.115,4,3,0.25,207.14,0.00,N/A
|
||||||
|
104.16.226.129,4,3,0.25,207.18,0.00,N/A
|
||||||
|
104.25.115.147,4,3,0.25,207.34,0.00,N/A
|
||||||
|
172.65.84.239,4,3,0.25,207.39,0.00,N/A
|
||||||
|
104.25.154.250,4,3,0.25,207.40,0.00,N/A
|
||||||
|
104.16.190.135,4,3,0.25,207.60,0.00,N/A
|
||||||
|
172.65.101.45,4,3,0.25,207.72,0.00,N/A
|
||||||
|
104.27.48.85,4,3,0.25,207.91,0.00,N/A
|
||||||
|
104.25.152.164,4,3,0.25,208.62,0.00,N/A
|
||||||
|
172.65.166.48,4,3,0.25,208.71,0.00,N/A
|
||||||
|
104.25.146.130,4,3,0.25,208.94,0.00,N/A
|
||||||
|
104.25.28.221,4,3,0.25,209.34,0.00,N/A
|
||||||
|
172.65.237.6,4,3,0.25,209.59,0.00,N/A
|
||||||
|
104.24.252.194,4,3,0.25,209.71,0.00,N/A
|
||||||
|
104.25.46.121,4,3,0.25,210.02,0.00,N/A
|
||||||
|
104.25.169.246,4,3,0.25,210.61,0.00,N/A
|
||||||
|
104.25.161.66,4,3,0.25,210.67,0.00,N/A
|
||||||
|
104.27.207.21,4,3,0.25,210.90,0.00,N/A
|
||||||
|
104.16.198.181,4,3,0.25,210.97,0.00,N/A
|
||||||
|
104.23.100.117,4,3,0.25,211.25,0.00,N/A
|
||||||
|
162.159.1.252,4,3,0.25,211.26,0.00,N/A
|
||||||
|
172.67.23.7,4,3,0.25,211.28,0.00,N/A
|
||||||
|
162.159.8.88,4,3,0.25,211.35,0.00,N/A
|
||||||
|
172.65.135.249,4,3,0.25,211.63,0.00,N/A
|
||||||
|
172.65.165.6,4,3,0.25,211.69,0.00,N/A
|
||||||
|
104.19.64.35,4,3,0.25,212.13,0.00,N/A
|
||||||
|
104.19.46.41,4,3,0.25,212.23,0.00,N/A
|
||||||
|
104.25.120.222,4,3,0.25,212.38,0.00,N/A
|
||||||
|
104.21.69.157,4,3,0.25,212.45,0.00,N/A
|
||||||
|
190.93.244.83,4,3,0.25,212.70,0.00,N/A
|
||||||
|
104.19.178.229,4,3,0.25,212.79,0.00,N/A
|
||||||
|
104.25.122.149,4,3,0.25,213.01,0.00,N/A
|
||||||
|
104.25.17.46,4,3,0.25,213.05,0.00,N/A
|
||||||
|
104.17.8.199,4,3,0.25,213.24,0.00,N/A
|
||||||
|
104.25.109.0,4,3,0.25,213.25,0.00,N/A
|
||||||
|
104.16.207.56,4,3,0.25,213.36,0.00,N/A
|
||||||
|
172.67.130.27,4,3,0.25,214.47,0.00,N/A
|
||||||
|
172.64.38.0,4,3,0.25,215.75,0.00,N/A
|
||||||
|
198.41.198.1,4,3,0.25,216.23,0.00,N/A
|
||||||
|
104.18.205.192,4,3,0.25,216.30,0.00,N/A
|
||||||
|
104.18.239.237,4,3,0.25,216.80,0.00,N/A
|
||||||
|
172.65.163.87,4,3,0.25,216.98,0.00,N/A
|
||||||
|
104.19.39.46,4,3,0.25,217.28,0.00,N/A
|
||||||
|
104.21.46.108,4,3,0.25,217.74,0.00,N/A
|
||||||
|
104.19.8.219,4,3,0.25,217.91,0.00,N/A
|
||||||
|
162.159.13.95,4,3,0.25,218.47,0.00,N/A
|
||||||
|
172.65.232.188,4,3,0.25,218.78,0.00,N/A
|
||||||
|
104.17.22.192,4,3,0.25,219.23,0.00,N/A
|
||||||
|
172.67.252.113,4,3,0.25,219.49,0.00,N/A
|
||||||
|
104.19.56.193,4,3,0.25,220.02,0.00,N/A
|
||||||
|
104.21.26.80,4,3,0.25,220.23,0.00,N/A
|
||||||
|
104.21.24.120,4,3,0.25,221.44,0.00,N/A
|
||||||
|
104.19.33.84,4,3,0.25,221.66,0.00,N/A
|
||||||
|
172.67.143.82,4,3,0.25,222.08,0.00,N/A
|
||||||
|
172.67.254.234,4,3,0.25,222.35,0.00,N/A
|
||||||
|
104.21.112.16,4,3,0.25,223.11,0.00,N/A
|
||||||
|
104.19.21.218,4,3,0.25,223.37,0.00,N/A
|
||||||
|
104.19.73.167,4,3,0.25,223.90,0.00,N/A
|
||||||
|
104.21.23.135,4,3,0.25,224.35,0.00,N/A
|
||||||
|
104.16.234.1,4,3,0.25,224.35,0.00,N/A
|
||||||
|
104.19.48.44,4,3,0.25,224.48,0.00,N/A
|
||||||
|
172.67.207.103,4,3,0.25,224.66,0.00,N/A
|
||||||
|
172.67.221.88,4,3,0.25,224.72,0.00,N/A
|
||||||
|
104.21.88.25,4,3,0.25,225.10,0.00,N/A
|
||||||
|
104.16.218.194,4,3,0.25,225.40,0.00,N/A
|
||||||
|
104.17.134.211,4,3,0.25,225.62,0.00,N/A
|
||||||
|
172.67.159.242,4,3,0.25,225.83,0.00,N/A
|
||||||
|
104.17.21.1,4,3,0.25,227.12,0.00,N/A
|
||||||
|
104.18.209.181,4,3,0.25,227.46,0.00,N/A
|
||||||
|
172.67.244.246,4,3,0.25,227.66,0.00,N/A
|
||||||
|
172.67.253.56,4,3,0.25,228.01,0.00,N/A
|
||||||
|
172.67.247.4,4,3,0.25,228.89,0.00,N/A
|
||||||
|
104.16.169.161,4,3,0.25,228.97,0.00,N/A
|
||||||
|
172.67.218.10,4,3,0.25,229.20,0.00,N/A
|
||||||
|
104.21.70.108,4,3,0.25,229.34,0.00,N/A
|
||||||
|
172.67.223.187,4,3,0.25,229.65,0.00,N/A
|
||||||
|
172.65.213.174,4,3,0.25,230.05,0.00,N/A
|
||||||
|
104.21.43.192,4,3,0.25,230.10,0.00,N/A
|
||||||
|
104.21.14.60,4,3,0.25,230.30,0.00,N/A
|
||||||
|
172.67.241.43,4,3,0.25,230.34,0.00,N/A
|
||||||
|
104.17.33.86,4,3,0.25,231.27,0.00,N/A
|
||||||
|
172.67.140.236,4,3,0.25,231.73,0.00,N/A
|
||||||
|
104.19.36.71,4,3,0.25,232.24,0.00,N/A
|
||||||
|
104.18.204.121,4,3,0.25,232.55,0.00,N/A
|
||||||
|
172.67.199.61,4,3,0.25,232.65,0.00,N/A
|
||||||
|
104.21.9.13,4,3,0.25,233.36,0.00,N/A
|
||||||
|
104.19.160.32,4,3,0.25,234.72,0.00,N/A
|
||||||
|
104.21.58.175,4,3,0.25,234.77,0.00,N/A
|
||||||
|
172.67.220.236,4,3,0.25,234.79,0.00,N/A
|
||||||
|
162.159.7.161,4,3,0.25,235.58,0.00,N/A
|
||||||
|
104.19.84.157,4,3,0.25,235.61,0.00,N/A
|
||||||
|
104.16.211.208,4,3,0.25,235.86,0.00,N/A
|
||||||
|
104.18.243.77,4,3,0.25,237.12,0.00,N/A
|
||||||
|
172.67.217.240,4,3,0.25,238.03,0.00,N/A
|
||||||
|
172.67.219.51,4,3,0.25,238.64,0.00,N/A
|
||||||
|
104.19.75.223,4,3,0.25,239.77,0.00,N/A
|
||||||
|
104.21.39.161,4,3,0.25,239.78,0.00,N/A
|
||||||
|
172.67.250.234,4,3,0.25,239.85,0.00,N/A
|
||||||
|
104.21.125.50,4,3,0.25,240.77,0.00,N/A
|
||||||
|
104.17.40.37,4,3,0.25,241.11,0.00,N/A
|
||||||
|
104.21.61.36,4,3,0.25,241.76,0.00,N/A
|
||||||
|
172.67.246.106,4,3,0.25,242.09,0.00,N/A
|
||||||
|
172.67.255.212,4,3,0.25,243.02,0.00,N/A
|
||||||
|
104.16.228.223,4,3,0.25,243.10,0.00,N/A
|
||||||
|
104.21.63.40,4,3,0.25,243.37,0.00,N/A
|
||||||
|
104.19.79.26,4,3,0.25,243.66,0.00,N/A
|
||||||
|
104.21.90.89,4,3,0.25,245.12,0.00,N/A
|
||||||
|
104.21.91.252,4,3,0.25,248.01,0.00,N/A
|
||||||
|
104.19.99.114,4,3,0.25,248.71,0.00,N/A
|
||||||
|
104.18.220.135,4,3,0.25,248.95,0.00,N/A
|
||||||
|
104.17.156.56,4,3,0.25,249.01,0.00,N/A
|
||||||
|
104.16.156.57,4,3,0.25,249.55,0.00,N/A
|
||||||
|
104.19.13.228,4,3,0.25,250.32,0.00,N/A
|
||||||
|
104.16.223.248,4,3,0.25,250.33,0.00,N/A
|
||||||
|
104.21.84.7,4,3,0.25,253.21,0.00,N/A
|
||||||
|
104.21.32.10,4,3,0.25,254.79,0.00,N/A
|
||||||
|
104.19.52.148,4,3,0.25,255.58,0.00,N/A
|
||||||
|
188.114.99.114,4,3,0.25,256.25,0.00,N/A
|
||||||
|
162.159.26.85,4,3,0.25,257.47,0.00,N/A
|
||||||
|
104.17.24.239,4,3,0.25,259.24,0.00,N/A
|
||||||
|
104.17.46.40,4,3,0.25,259.94,0.00,N/A
|
||||||
|
104.16.237.232,4,3,0.25,263.49,0.00,N/A
|
||||||
|
104.16.172.243,4,3,0.25,266.25,0.00,N/A
|
||||||
|
104.16.244.83,4,3,0.25,269.50,0.00,N/A
|
||||||
|
104.16.248.22,4,3,0.25,270.58,0.00,N/A
|
||||||
|
104.19.9.231,4,3,0.25,274.11,0.00,N/A
|
||||||
|
104.17.26.25,4,3,0.25,277.95,0.00,N/A
|
||||||
|
104.18.143.96,4,3,0.25,280.35,0.00,N/A
|
||||||
|
104.16.254.154,4,3,0.25,290.76,0.00,N/A
|
||||||
|
104.16.128.24,4,3,0.25,295.17,0.00,N/A
|
||||||
|
104.16.217.215,4,3,0.25,296.90,0.00,N/A
|
||||||
|
104.16.243.152,4,3,0.25,298.05,0.00,N/A
|
||||||
|
104.19.74.232,4,3,0.25,301.24,0.00,N/A
|
||||||
|
104.16.186.132,4,3,0.25,301.86,0.00,N/A
|
||||||
|
104.19.22.68,4,3,0.25,305.49,0.00,N/A
|
||||||
|
104.17.25.230,4,3,0.25,305.60,0.00,N/A
|
||||||
|
162.159.12.250,4,3,0.25,333.08,0.00,N/A
|
||||||
|
104.16.232.127,4,2,0.50,165.77,0.00,N/A
|
||||||
|
172.65.211.195,4,2,0.50,166.03,0.00,N/A
|
||||||
|
104.16.210.63,4,2,0.50,166.28,0.00,N/A
|
||||||
|
104.18.202.245,4,2,0.50,167.10,0.00,N/A
|
||||||
|
104.19.32.244,4,2,0.50,168.25,0.00,N/A
|
||||||
|
104.19.47.130,4,2,0.50,168.65,0.00,N/A
|
||||||
|
104.19.28.39,4,2,0.50,168.73,0.00,N/A
|
||||||
|
104.19.62.128,4,2,0.50,169.06,0.00,N/A
|
||||||
|
104.18.236.37,4,2,0.50,169.51,0.00,N/A
|
||||||
|
104.16.213.127,4,2,0.50,169.63,0.00,N/A
|
||||||
|
104.19.5.114,4,2,0.50,169.63,0.00,N/A
|
||||||
|
172.65.214.236,4,2,0.50,169.80,0.00,N/A
|
||||||
|
104.19.34.192,4,2,0.50,170.53,0.00,N/A
|
||||||
|
172.65.217.13,4,2,0.50,170.61,0.00,N/A
|
||||||
|
104.18.255.70,4,2,0.50,171.30,0.00,N/A
|
||||||
|
104.18.228.127,4,2,0.50,171.34,0.00,N/A
|
||||||
|
104.16.196.113,4,2,0.50,171.73,0.00,N/A
|
||||||
|
104.18.200.77,4,2,0.50,171.82,0.00,N/A
|
||||||
|
104.19.29.149,4,2,0.50,172.26,0.00,N/A
|
||||||
|
104.18.246.66,4,2,0.50,172.34,0.00,N/A
|
||||||
|
104.19.53.109,4,2,0.50,172.43,0.00,N/A
|
||||||
|
104.17.9.181,4,2,0.50,172.74,0.00,N/A
|
||||||
|
172.65.208.209,4,2,0.50,173.03,0.00,N/A
|
||||||
|
104.18.219.64,4,2,0.50,173.10,0.00,N/A
|
||||||
|
104.19.6.132,4,2,0.50,173.37,0.00,N/A
|
||||||
|
104.19.66.0,4,2,0.50,173.63,0.00,N/A
|
||||||
|
172.65.193.184,4,2,0.50,173.81,0.00,N/A
|
||||||
|
104.19.25.178,4,2,0.50,174.11,0.00,N/A
|
||||||
|
104.16.230.87,4,2,0.50,174.42,0.00,N/A
|
||||||
|
104.16.225.15,4,2,0.50,174.97,0.00,N/A
|
||||||
|
104.17.6.82,4,2,0.50,175.80,0.00,N/A
|
||||||
|
104.16.168.243,4,2,0.50,176.35,0.00,N/A
|
||||||
|
162.159.46.227,4,2,0.50,176.41,0.00,N/A
|
||||||
|
104.18.247.48,4,2,0.50,176.50,0.00,N/A
|
||||||
|
104.19.19.210,4,2,0.50,177.17,0.00,N/A
|
||||||
|
172.65.215.191,4,2,0.50,177.38,0.00,N/A
|
||||||
|
172.65.228.103,4,2,0.50,177.92,0.00,N/A
|
||||||
|
104.19.54.104,4,2,0.50,178.14,0.00,N/A
|
||||||
|
172.65.209.80,4,2,0.50,178.23,0.00,N/A
|
||||||
|
172.64.149.174,4,2,0.50,178.96,0.00,N/A
|
||||||
|
108.162.194.75,4,2,0.50,179.04,0.00,N/A
|
||||||
|
104.27.113.13,4,2,0.50,179.52,0.00,N/A
|
||||||
|
104.18.198.55,4,2,0.50,180.07,0.00,N/A
|
||||||
|
104.19.60.74,4,2,0.50,180.28,0.00,N/A
|
||||||
|
104.17.67.158,4,2,0.50,180.45,0.00,N/A
|
||||||
|
104.19.241.60,4,2,0.50,180.59,0.00,N/A
|
||||||
|
172.65.131.127,4,2,0.50,180.84,0.00,N/A
|
||||||
|
104.16.181.177,4,2,0.50,181.33,0.00,N/A
|
||||||
|
172.67.111.160,4,2,0.50,181.90,0.00,N/A
|
||||||
|
104.27.89.177,4,2,0.50,182.25,0.00,N/A
|
||||||
|
104.25.1.205,4,2,0.50,182.37,0.00,N/A
|
||||||
|
104.27.121.46,4,2,0.50,182.55,0.00,N/A
|
||||||
|
104.25.156.2,4,2,0.50,182.89,0.00,N/A
|
||||||
|
172.65.242.204,4,2,0.50,183.62,0.00,N/A
|
||||||
|
172.65.161.140,4,2,0.50,183.64,0.00,N/A
|
||||||
|
104.27.94.162,4,2,0.50,184.37,0.00,N/A
|
||||||
|
104.25.145.241,4,2,0.50,184.61,0.00,N/A
|
||||||
|
104.25.167.30,4,2,0.50,185.29,0.00,N/A
|
||||||
|
104.25.61.52,4,2,0.50,185.51,0.00,N/A
|
||||||
|
104.16.236.16,4,2,0.50,186.96,0.00,N/A
|
||||||
|
172.65.210.45,4,2,0.50,186.99,0.00,N/A
|
||||||
|
104.16.204.13,4,2,0.50,187.11,0.00,N/A
|
||||||
|
172.64.146.66,4,2,0.50,187.42,0.00,N/A
|
||||||
|
172.65.134.158,4,2,0.50,187.58,0.00,N/A
|
||||||
|
172.65.226.202,4,2,0.50,187.86,0.00,N/A
|
||||||
|
104.25.103.134,4,2,0.50,189.11,0.00,N/A
|
||||||
|
172.65.190.155,4,2,0.50,189.11,0.00,N/A
|
||||||
|
104.27.73.8,4,2,0.50,189.20,0.00,N/A
|
||||||
|
172.65.92.58,4,2,0.50,189.23,0.00,N/A
|
||||||
|
104.25.125.82,4,2,0.50,189.24,0.00,N/A
|
||||||
|
104.27.124.170,4,2,0.50,189.25,0.00,N/A
|
||||||
|
104.27.114.18,4,2,0.50,189.58,0.00,N/A
|
||||||
|
104.25.204.1,4,2,0.50,189.60,0.00,N/A
|
||||||
|
104.25.147.35,4,2,0.50,189.62,0.00,N/A
|
||||||
|
104.25.30.42,4,2,0.50,189.70,0.00,N/A
|
||||||
|
104.27.53.231,4,2,0.50,189.94,0.00,N/A
|
||||||
|
104.23.112.114,4,2,0.50,190.05,0.00,N/A
|
||||||
|
104.25.150.43,4,2,0.50,190.41,0.00,N/A
|
||||||
|
104.17.28.28,4,2,0.50,190.48,0.00,N/A
|
||||||
|
104.25.163.122,4,2,0.50,190.63,0.00,N/A
|
||||||
|
104.25.92.129,4,2,0.50,190.84,0.00,N/A
|
||||||
|
104.16.174.195,4,2,0.50,191.23,0.00,N/A
|
||||||
|
172.65.180.91,4,2,0.50,191.24,0.00,N/A
|
||||||
|
172.65.191.61,4,2,0.50,191.38,0.00,N/A
|
||||||
|
104.25.129.229,4,2,0.50,191.48,0.00,N/A
|
||||||
|
172.65.140.27,4,2,0.50,191.66,0.00,N/A
|
||||||
|
104.27.59.198,4,2,0.50,191.79,0.00,N/A
|
||||||
|
104.27.86.147,4,2,0.50,191.95,0.00,N/A
|
||||||
|
104.27.123.120,4,2,0.50,192.21,0.00,N/A
|
||||||
|
162.159.51.244,4,2,0.50,192.76,0.00,N/A
|
||||||
|
104.16.197.186,4,2,0.50,193.16,0.00,N/A
|
||||||
|
104.25.79.107,4,2,0.50,193.32,0.00,N/A
|
||||||
|
172.65.130.170,4,2,0.50,194.25,0.00,N/A
|
||||||
|
104.27.72.254,4,2,0.50,194.69,0.00,N/A
|
||||||
|
172.65.189.137,4,2,0.50,195.25,0.00,N/A
|
||||||
|
104.25.114.125,4,2,0.50,195.28,0.00,N/A
|
||||||
|
104.25.153.60,4,2,0.50,195.49,0.00,N/A
|
||||||
|
104.27.95.5,4,2,0.50,196.06,0.00,N/A
|
||||||
|
172.65.82.218,4,2,0.50,196.10,0.00,N/A
|
||||||
|
104.16.202.120,4,2,0.50,196.33,0.00,N/A
|
||||||
|
104.27.54.21,4,2,0.50,196.33,0.00,N/A
|
||||||
|
172.65.127.66,4,2,0.50,196.40,0.00,N/A
|
||||||
|
172.66.145.171,4,2,0.50,196.63,0.00,N/A
|
||||||
|
104.27.112.128,4,2,0.50,196.68,0.00,N/A
|
||||||
|
104.25.107.63,4,2,0.50,197.10,0.00,N/A
|
||||||
|
104.23.115.58,4,2,0.50,197.14,0.00,N/A
|
||||||
|
172.67.147.131,4,2,0.50,197.27,0.00,N/A
|
||||||
|
104.27.65.58,4,2,0.50,197.46,0.00,N/A
|
||||||
|
104.25.38.160,4,2,0.50,197.66,0.00,N/A
|
||||||
|
104.16.235.17,4,2,0.50,197.72,0.00,N/A
|
||||||
|
104.25.166.221,4,2,0.50,197.78,0.00,N/A
|
||||||
|
104.25.39.109,4,2,0.50,197.83,0.00,N/A
|
||||||
|
104.25.21.254,4,2,0.50,198.45,0.00,N/A
|
||||||
|
172.65.172.134,4,2,0.50,198.48,0.00,N/A
|
||||||
|
104.25.102.240,4,2,0.50,198.58,0.00,N/A
|
||||||
|
104.25.202.146,4,2,0.50,198.61,0.00,N/A
|
||||||
|
104.25.128.235,4,2,0.50,198.76,0.00,N/A
|
||||||
|
104.25.19.188,4,2,0.50,199.01,0.00,N/A
|
||||||
|
104.25.91.90,4,2,0.50,199.07,0.00,N/A
|
||||||
|
104.16.206.246,4,2,0.50,199.23,0.00,N/A
|
||||||
|
172.65.168.169,4,2,0.50,199.43,0.00,N/A
|
||||||
|
172.65.139.219,4,2,0.50,200.38,0.00,N/A
|
||||||
|
104.27.127.17,4,2,0.50,200.40,0.00,N/A
|
||||||
|
172.67.114.185,4,2,0.50,201.63,0.00,N/A
|
||||||
|
104.24.245.37,4,2,0.50,201.73,0.00,N/A
|
||||||
|
104.18.233.82,4,2,0.50,201.99,0.00,N/A
|
||||||
|
104.23.134.70,4,2,0.50,202.04,0.00,N/A
|
||||||
|
104.27.45.21,4,2,0.50,202.21,0.00,N/A
|
||||||
|
104.25.76.224,4,2,0.50,202.43,0.00,N/A
|
||||||
|
104.25.69.222,4,2,0.50,202.52,0.00,N/A
|
||||||
|
172.65.244.105,4,2,0.50,202.69,0.00,N/A
|
||||||
|
172.66.196.154,4,2,0.50,202.74,0.00,N/A
|
||||||
|
172.65.167.167,4,2,0.50,202.94,0.00,N/A
|
||||||
|
104.25.117.55,4,2,0.50,203.07,0.00,N/A
|
||||||
|
104.25.132.250,4,2,0.50,203.24,0.00,N/A
|
||||||
|
104.19.23.120,4,2,0.50,203.25,0.00,N/A
|
||||||
|
172.67.193.52,4,2,0.50,203.42,0.00,N/A
|
||||||
|
104.16.231.173,4,2,0.50,203.75,0.00,N/A
|
||||||
|
104.25.111.179,4,2,0.50,203.81,0.00,N/A
|
||||||
|
172.65.145.26,4,2,0.50,203.95,0.00,N/A
|
||||||
|
172.67.122.116,4,2,0.50,204.07,0.00,N/A
|
||||||
|
104.27.87.198,4,2,0.50,204.25,0.00,N/A
|
||||||
|
104.16.250.187,4,2,0.50,204.32,0.00,N/A
|
||||||
|
172.65.192.175,4,2,0.50,204.45,0.00,N/A
|
||||||
|
172.65.178.57,4,2,0.50,204.62,0.00,N/A
|
||||||
|
104.27.43.169,4,2,0.50,204.75,0.00,N/A
|
||||||
|
172.67.112.161,4,2,0.50,204.87,0.00,N/A
|
||||||
|
162.159.42.97,4,2,0.50,205.12,0.00,N/A
|
||||||
|
172.67.238.4,4,2,0.50,205.90,0.00,N/A
|
||||||
|
172.67.243.192,4,2,0.50,205.92,0.00,N/A
|
||||||
|
104.23.98.14,4,2,0.50,205.99,0.00,N/A
|
||||||
|
104.27.71.131,4,2,0.50,206.35,0.00,N/A
|
||||||
|
104.25.182.99,4,2,0.50,206.55,0.00,N/A
|
||||||
|
104.17.15.149,4,2,0.50,206.97,0.00,N/A
|
||||||
|
104.25.89.4,4,2,0.50,207.06,0.00,N/A
|
||||||
|
104.27.88.10,4,2,0.50,207.06,0.00,N/A
|
||||||
|
104.25.78.37,4,2,0.50,208.03,0.00,N/A
|
||||||
|
104.23.101.18,4,2,0.50,208.19,0.00,N/A
|
||||||
|
104.25.162.191,4,2,0.50,208.30,0.00,N/A
|
||||||
|
104.16.209.136,4,2,0.50,208.41,0.00,N/A
|
||||||
|
104.27.46.19,4,2,0.50,209.08,0.00,N/A
|
||||||
|
104.25.148.28,4,2,0.50,209.50,0.00,N/A
|
||||||
|
104.25.72.41,4,2,0.50,209.60,0.00,N/A
|
||||||
|
104.27.69.216,4,2,0.50,209.81,0.00,N/A
|
||||||
|
172.65.124.65,4,2,0.50,209.92,0.00,N/A
|
||||||
|
104.25.123.30,4,2,0.50,210.09,0.00,N/A
|
||||||
|
104.25.143.37,4,2,0.50,210.44,0.00,N/A
|
||||||
|
104.27.74.218,4,2,0.50,210.49,0.00,N/A
|
||||||
|
104.25.71.180,4,2,0.50,211.95,0.00,N/A
|
||||||
|
104.25.110.235,4,2,0.50,212.26,0.00,N/A
|
||||||
|
172.67.230.47,4,2,0.50,212.26,0.00,N/A
|
||||||
|
104.25.106.176,4,2,0.50,212.27,0.00,N/A
|
||||||
|
104.25.119.239,4,2,0.50,212.62,0.00,N/A
|
||||||
|
104.25.18.74,4,2,0.50,213.15,0.00,N/A
|
||||||
|
104.24.254.26,4,2,0.50,213.23,0.00,N/A
|
||||||
|
172.67.226.119,4,2,0.50,213.24,0.00,N/A
|
||||||
|
172.67.179.228,4,2,0.50,213.75,0.00,N/A
|
||||||
|
104.25.159.108,4,2,0.50,214.22,0.00,N/A
|
||||||
|
172.67.117.58,4,2,0.50,214.58,0.00,N/A
|
||||||
|
104.27.56.251,4,2,0.50,214.91,0.00,N/A
|
||||||
|
172.67.224.75,4,2,0.50,215.10,0.00,N/A
|
||||||
|
172.65.129.88,4,2,0.50,215.35,0.00,N/A
|
||||||
|
172.67.249.143,4,2,0.50,215.54,0.00,N/A
|
||||||
|
172.67.43.168,4,2,0.50,216.97,0.00,N/A
|
||||||
|
172.67.227.148,4,2,0.50,217.04,0.00,N/A
|
||||||
|
172.67.36.28,4,2,0.50,218.39,0.00,N/A
|
||||||
|
104.18.254.179,4,2,0.50,218.56,0.00,N/A
|
||||||
|
172.67.144.231,4,2,0.50,219.65,0.00,N/A
|
||||||
|
104.21.100.208,4,2,0.50,219.97,0.00,N/A
|
||||||
|
104.25.70.253,4,2,0.50,220.19,0.00,N/A
|
||||||
|
104.21.94.224,4,2,0.50,220.34,0.00,N/A
|
||||||
|
104.21.89.3,4,2,0.50,220.83,0.00,N/A
|
||||||
|
172.67.229.25,4,2,0.50,222.55,0.00,N/A
|
||||||
|
104.21.68.236,4,2,0.50,222.66,0.00,N/A
|
||||||
|
172.67.233.144,4,2,0.50,222.73,0.00,N/A
|
||||||
|
104.21.62.90,4,2,0.50,223.24,0.00,N/A
|
||||||
|
172.67.245.123,4,2,0.50,223.47,0.00,N/A
|
||||||
|
104.21.36.9,4,2,0.50,223.48,0.00,N/A
|
||||||
|
198.41.214.181,4,2,0.50,223.61,0.00,N/A
|
||||||
|
172.67.196.9,4,2,0.50,223.67,0.00,N/A
|
||||||
|
172.67.171.87,4,2,0.50,224.33,0.00,N/A
|
||||||
|
104.21.73.62,4,2,0.50,224.38,0.00,N/A
|
||||||
|
104.18.214.143,4,2,0.50,225.16,0.00,N/A
|
||||||
|
104.19.16.151,4,2,0.50,225.38,0.00,N/A
|
||||||
|
172.67.133.180,4,2,0.50,225.94,0.00,N/A
|
||||||
|
104.21.45.36,4,2,0.50,226.22,0.00,N/A
|
||||||
|
104.17.11.9,4,2,0.50,227.71,0.00,N/A
|
||||||
|
172.67.150.56,4,2,0.50,227.74,0.00,N/A
|
||||||
|
104.21.22.214,4,2,0.50,227.81,0.00,N/A
|
||||||
|
104.21.79.9,4,2,0.50,228.13,0.00,N/A
|
||||||
|
104.17.12.216,4,2,0.50,228.19,0.00,N/A
|
||||||
|
104.21.51.135,4,2,0.50,228.24,0.00,N/A
|
||||||
|
104.21.35.76,4,2,0.50,228.26,0.00,N/A
|
||||||
|
104.21.47.241,4,2,0.50,228.45,0.00,N/A
|
||||||
|
172.67.236.31,4,2,0.50,228.80,0.00,N/A
|
||||||
|
172.67.148.83,4,2,0.50,229.02,0.00,N/A
|
||||||
|
104.16.227.253,4,2,0.50,229.10,0.00,N/A
|
||||||
|
172.67.239.243,4,2,0.50,229.11,0.00,N/A
|
||||||
|
172.67.184.224,4,2,0.50,229.43,0.00,N/A
|
||||||
|
172.67.200.95,4,2,0.50,230.74,0.00,N/A
|
||||||
|
104.21.117.130,4,2,0.50,231.99,0.00,N/A
|
||||||
|
104.21.38.25,4,2,0.50,232.07,0.00,N/A
|
||||||
|
172.67.235.165,4,2,0.50,232.51,0.00,N/A
|
||||||
|
172.67.191.89,4,2,0.50,232.92,0.00,N/A
|
||||||
|
104.21.78.31,4,2,0.50,233.48,0.00,N/A
|
||||||
|
172.64.235.13,4,2,0.50,233.52,0.00,N/A
|
||||||
|
104.17.5.111,4,2,0.50,234.38,0.00,N/A
|
||||||
|
172.67.163.174,4,2,0.50,234.60,0.00,N/A
|
||||||
|
172.65.23.28,4,2,0.50,235.39,0.00,N/A
|
||||||
|
104.21.77.247,4,2,0.50,235.64,0.00,N/A
|
||||||
|
104.21.95.112,4,2,0.50,235.92,0.00,N/A
|
||||||
|
172.67.234.190,4,2,0.50,236.33,0.00,N/A
|
||||||
|
104.21.80.214,4,2,0.50,236.43,0.00,N/A
|
||||||
|
172.67.187.174,4,2,0.50,236.78,0.00,N/A
|
||||||
|
104.21.72.115,4,2,0.50,236.98,0.00,N/A
|
||||||
|
104.16.249.247,4,2,0.50,237.12,0.00,N/A
|
||||||
|
172.67.222.130,4,2,0.50,237.44,0.00,N/A
|
||||||
|
172.67.232.134,4,2,0.50,237.45,0.00,N/A
|
||||||
|
172.67.186.23,4,2,0.50,237.52,0.00,N/A
|
||||||
|
172.67.146.102,4,2,0.50,237.54,0.00,N/A
|
||||||
|
104.21.71.65,4,2,0.50,238.21,0.00,N/A
|
||||||
|
172.67.240.219,4,2,0.50,238.31,0.00,N/A
|
||||||
|
104.19.31.159,4,2,0.50,238.99,0.00,N/A
|
||||||
|
172.67.209.179,4,2,0.50,239.14,0.00,N/A
|
||||||
|
104.18.203.126,4,2,0.50,240.80,0.00,N/A
|
||||||
|
172.67.225.16,4,2,0.50,241.65,0.00,N/A
|
||||||
|
104.19.20.215,4,2,0.50,241.67,0.00,N/A
|
||||||
|
104.18.199.178,4,2,0.50,241.70,0.00,N/A
|
||||||
|
104.21.93.155,4,2,0.50,242.14,0.00,N/A
|
||||||
|
104.21.105.247,4,2,0.50,242.39,0.00,N/A
|
||||||
|
172.67.237.203,4,2,0.50,243.20,0.00,N/A
|
||||||
|
104.21.67.19,4,2,0.50,244.70,0.00,N/A
|
||||||
|
172.67.129.156,4,2,0.50,245.99,0.00,N/A
|
||||||
|
172.67.242.188,4,2,0.50,246.07,0.00,N/A
|
||||||
|
172.67.201.160,4,2,0.50,246.09,0.00,N/A
|
||||||
|
172.67.228.250,4,2,0.50,246.47,0.00,N/A
|
||||||
|
172.67.195.214,4,2,0.50,247.36,0.00,N/A
|
||||||
|
104.17.23.35,4,2,0.50,250.13,0.00,N/A
|
||||||
|
104.16.252.241,4,2,0.50,251.28,0.00,N/A
|
||||||
|
104.18.249.186,4,2,0.50,252.02,0.00,N/A
|
||||||
|
172.67.248.187,4,2,0.50,252.07,0.00,N/A
|
||||||
|
172.67.131.46,4,2,0.50,252.28,0.00,N/A
|
||||||
|
104.17.0.9,4,2,0.50,252.95,0.00,N/A
|
||||||
|
172.67.204.103,4,2,0.50,254.53,0.00,N/A
|
||||||
|
104.16.179.117,4,2,0.50,254.91,0.00,N/A
|
||||||
|
172.65.12.220,4,2,0.50,256.55,0.00,N/A
|
||||||
|
172.67.176.156,4,2,0.50,256.81,0.00,N/A
|
||||||
|
172.65.231.8,4,2,0.50,258.67,0.00,N/A
|
||||||
|
104.16.203.254,4,2,0.50,262.04,0.00,N/A
|
||||||
|
104.19.41.46,4,2,0.50,265.39,0.00,N/A
|
||||||
|
104.16.221.213,4,2,0.50,268.02,0.00,N/A
|
||||||
|
104.19.97.228,4,2,0.50,269.77,0.00,N/A
|
||||||
|
104.19.38.43,4,2,0.50,275.62,0.00,N/A
|
||||||
|
104.16.205.95,4,2,0.50,282.59,0.00,N/A
|
||||||
|
162.159.0.137,4,2,0.50,283.38,0.00,N/A
|
||||||
|
162.159.32.3,4,2,0.50,291.78,0.00,N/A
|
||||||
|
162.159.36.48,4,2,0.50,296.85,0.00,N/A
|
||||||
|
104.16.182.109,4,2,0.50,308.08,0.00,N/A
|
||||||
|
104.17.1.92,4,2,0.50,315.93,0.00,N/A
|
||||||
|
104.19.72.245,4,2,0.50,319.16,0.00,N/A
|
||||||
|
104.16.229.45,4,2,0.50,321.22,0.00,N/A
|
||||||
|
190.93.245.152,4,2,0.50,336.90,0.00,N/A
|
||||||
|
104.17.7.145,4,2,0.50,349.57,0.00,N/A
|
||||||
|
104.18.213.14,4,1,0.75,163.67,0.00,N/A
|
||||||
|
104.19.30.199,4,1,0.75,164.04,0.00,N/A
|
||||||
|
104.18.252.219,4,1,0.75,164.51,0.00,N/A
|
||||||
|
104.18.237.110,4,1,0.75,165.68,0.00,N/A
|
||||||
|
104.16.188.172,4,1,0.75,166.77,0.00,N/A
|
||||||
|
104.18.217.241,4,1,0.75,167.06,0.00,N/A
|
||||||
|
172.65.246.132,4,1,0.75,168.13,0.00,N/A
|
||||||
|
104.19.65.194,4,1,0.75,168.13,0.00,N/A
|
||||||
|
104.16.214.194,4,1,0.75,168.14,0.00,N/A
|
||||||
|
104.16.199.146,4,1,0.75,169.13,0.00,N/A
|
||||||
|
104.16.187.85,4,1,0.75,169.21,0.00,N/A
|
||||||
|
104.18.201.59,4,1,0.75,169.94,0.00,N/A
|
||||||
|
104.16.200.21,4,1,0.75,170.40,0.00,N/A
|
||||||
|
104.19.7.172,4,1,0.75,171.16,0.00,N/A
|
||||||
|
104.16.233.87,4,1,0.75,171.19,0.00,N/A
|
||||||
|
104.18.241.152,4,1,0.75,171.42,0.00,N/A
|
||||||
|
104.18.212.123,4,1,0.75,171.42,0.00,N/A
|
||||||
|
104.16.239.52,4,1,0.75,171.47,0.00,N/A
|
||||||
|
104.18.244.113,4,1,0.75,171.79,0.00,N/A
|
||||||
|
172.65.227.240,4,1,0.75,171.86,0.00,N/A
|
||||||
|
172.65.251.201,4,1,0.75,172.89,0.00,N/A
|
||||||
|
104.17.75.226,4,1,0.75,173.25,0.00,N/A
|
||||||
|
104.18.223.101,4,1,0.75,173.25,0.00,N/A
|
||||||
|
104.18.229.11,4,1,0.75,173.40,0.00,N/A
|
||||||
|
104.19.37.75,4,1,0.75,173.70,0.00,N/A
|
||||||
|
104.16.177.246,4,1,0.75,174.08,0.00,N/A
|
||||||
|
104.18.218.96,4,1,0.75,174.15,0.00,N/A
|
||||||
|
104.16.240.184,4,1,0.75,174.20,0.00,N/A
|
||||||
|
104.16.241.21,4,1,0.75,174.25,0.00,N/A
|
||||||
|
104.21.237.45,4,1,0.75,176.08,0.00,N/A
|
||||||
|
104.17.30.19,4,1,0.75,176.25,0.00,N/A
|
||||||
|
104.18.225.76,4,1,0.75,176.46,0.00,N/A
|
||||||
|
172.65.229.96,4,1,0.75,176.57,0.00,N/A
|
||||||
|
104.19.26.118,4,1,0.75,176.57,0.00,N/A
|
||||||
|
104.17.45.234,4,1,0.75,176.78,0.00,N/A
|
||||||
|
172.65.219.91,4,1,0.75,176.96,0.00,N/A
|
||||||
|
198.41.223.198,4,1,0.75,177.06,0.00,N/A
|
||||||
|
172.65.222.206,4,1,0.75,178.64,0.00,N/A
|
||||||
|
190.93.246.193,4,1,0.75,179.66,0.00,N/A
|
||||||
|
104.25.75.19,4,1,0.75,179.71,0.00,N/A
|
||||||
|
104.18.226.224,4,1,0.75,180.63,0.00,N/A
|
||||||
|
108.162.193.248,4,1,0.75,181.13,0.00,N/A
|
||||||
|
104.25.31.119,4,1,0.75,181.94,0.00,N/A
|
||||||
|
172.67.110.213,4,1,0.75,182.15,0.00,N/A
|
||||||
|
162.159.41.63,4,1,0.75,182.80,0.00,N/A
|
||||||
|
104.25.8.235,4,1,0.75,182.84,0.00,N/A
|
||||||
|
104.25.105.177,4,1,0.75,182.97,0.00,N/A
|
||||||
|
104.25.29.162,4,1,0.75,183.02,0.00,N/A
|
||||||
|
172.67.118.15,4,1,0.75,183.07,0.00,N/A
|
||||||
|
172.65.174.192,4,1,0.75,183.14,0.00,N/A
|
||||||
|
104.25.50.20,4,1,0.75,183.59,0.00,N/A
|
||||||
|
104.21.239.56,4,1,0.75,183.60,0.00,N/A
|
||||||
|
172.65.159.113,4,1,0.75,183.75,0.00,N/A
|
||||||
|
104.25.41.41,4,1,0.75,183.86,0.00,N/A
|
||||||
|
104.25.168.83,4,1,0.75,184.24,0.00,N/A
|
||||||
|
104.23.123.173,4,1,0.75,184.85,0.00,N/A
|
||||||
|
104.23.116.70,4,1,0.75,184.85,0.00,N/A
|
||||||
|
104.25.170.252,4,1,0.75,184.94,0.00,N/A
|
||||||
|
104.27.42.251,4,1,0.75,185.19,0.00,N/A
|
||||||
|
172.65.234.41,4,1,0.75,185.35,0.00,N/A
|
||||||
|
104.16.7.148,4,1,0.75,185.90,0.00,N/A
|
||||||
|
162.159.128.78,4,1,0.75,185.91,0.00,N/A
|
||||||
|
104.25.85.217,4,1,0.75,186.13,0.00,N/A
|
||||||
|
104.25.23.29,4,1,0.75,186.50,0.00,N/A
|
||||||
|
104.23.141.211,4,1,0.75,186.78,0.00,N/A
|
||||||
|
104.27.76.127,4,1,0.75,187.87,0.00,N/A
|
||||||
|
104.16.157.22,4,1,0.75,188.21,0.00,N/A
|
||||||
|
104.25.178.192,4,1,0.75,189.04,0.00,N/A
|
||||||
|
104.17.241.221,4,1,0.75,190.77,0.00,N/A
|
||||||
|
104.19.96.75,4,1,0.75,190.80,0.00,N/A
|
||||||
|
162.159.245.6,4,1,0.75,191.48,0.00,N/A
|
||||||
|
104.23.129.59,4,1,0.75,192.33,0.00,N/A
|
||||||
|
162.159.243.54,4,1,0.75,192.36,0.00,N/A
|
||||||
|
104.27.92.55,4,1,0.75,192.62,0.00,N/A
|
||||||
|
104.25.65.123,4,1,0.75,192.95,0.00,N/A
|
||||||
|
104.25.157.69,4,1,0.75,193.41,0.00,N/A
|
||||||
|
172.65.158.237,4,1,0.75,194.02,0.00,N/A
|
||||||
|
104.19.2.132,4,1,0.75,194.04,0.00,N/A
|
||||||
|
104.23.114.211,4,1,0.75,194.68,0.00,N/A
|
||||||
|
104.27.52.65,4,1,0.75,194.82,0.00,N/A
|
||||||
|
104.25.116.110,4,1,0.75,194.86,0.00,N/A
|
||||||
|
172.65.179.128,4,1,0.75,194.91,0.00,N/A
|
||||||
|
104.25.124.114,4,1,0.75,195.15,0.00,N/A
|
||||||
|
104.25.0.192,4,1,0.75,195.22,0.00,N/A
|
||||||
|
104.25.86.81,4,1,0.75,195.43,0.00,N/A
|
||||||
|
104.25.144.144,4,1,0.75,195.68,0.00,N/A
|
||||||
|
104.25.10.30,4,1,0.75,195.76,0.00,N/A
|
||||||
|
172.65.173.33,4,1,0.75,195.82,0.00,N/A
|
||||||
|
104.25.173.172,4,1,0.75,196.16,0.00,N/A
|
||||||
|
104.25.73.32,4,1,0.75,196.44,0.00,N/A
|
||||||
|
104.27.75.93,4,1,0.75,196.77,0.00,N/A
|
||||||
|
104.27.91.117,4,1,0.75,197.12,0.00,N/A
|
||||||
|
104.25.140.229,4,1,0.75,197.32,0.00,N/A
|
||||||
|
172.65.185.64,4,1,0.75,197.33,0.00,N/A
|
||||||
|
104.25.66.201,4,1,0.75,197.39,0.00,N/A
|
||||||
|
172.65.142.109,4,1,0.75,197.51,0.00,N/A
|
||||||
|
172.65.175.0,4,1,0.75,197.54,0.00,N/A
|
||||||
|
172.65.164.223,4,1,0.75,197.54,0.00,N/A
|
||||||
|
104.16.102.58,4,1,0.75,197.81,0.00,N/A
|
||||||
|
172.65.133.234,4,1,0.75,197.83,0.00,N/A
|
||||||
|
104.25.133.24,4,1,0.75,197.83,0.00,N/A
|
||||||
|
104.25.81.166,4,1,0.75,197.91,0.00,N/A
|
||||||
|
172.67.134.70,4,1,0.75,198.12,0.00,N/A
|
||||||
|
104.25.127.129,4,1,0.75,198.41,0.00,N/A
|
||||||
|
104.27.68.180,4,1,0.75,198.52,0.00,N/A
|
||||||
|
104.25.113.193,4,1,0.75,198.67,0.00,N/A
|
||||||
|
104.27.61.7,4,1,0.75,198.77,0.00,N/A
|
||||||
|
172.65.156.55,4,1,0.75,198.83,0.00,N/A
|
||||||
|
172.65.132.228,4,1,0.75,199.10,0.00,N/A
|
||||||
|
104.23.106.123,4,1,0.75,199.24,0.00,N/A
|
||||||
|
172.67.172.253,4,1,0.75,199.41,0.00,N/A
|
||||||
|
172.67.113.106,4,1,0.75,199.41,0.00,N/A
|
||||||
|
104.18.151.71,4,1,0.75,199.45,0.00,N/A
|
||||||
|
104.25.44.230,4,1,0.75,199.53,0.00,N/A
|
||||||
|
172.65.183.239,4,1,0.75,199.54,0.00,N/A
|
||||||
|
104.21.16.108,4,1,0.75,200.13,0.00,N/A
|
||||||
|
172.65.188.8,4,1,0.75,200.15,0.00,N/A
|
||||||
|
172.65.160.83,4,1,0.75,200.30,0.00,N/A
|
||||||
|
104.27.78.3,4,1,0.75,200.46,0.00,N/A
|
||||||
|
104.27.80.42,4,1,0.75,200.69,0.00,N/A
|
||||||
|
104.27.82.19,4,1,0.75,200.93,0.00,N/A
|
||||||
|
104.18.150.215,4,1,0.75,201.21,0.00,N/A
|
||||||
|
104.25.82.130,4,1,0.75,201.26,0.00,N/A
|
||||||
|
172.67.132.213,4,1,0.75,201.42,0.00,N/A
|
||||||
|
104.25.165.110,4,1,0.75,203.09,0.00,N/A
|
||||||
|
172.67.116.7,4,1,0.75,204.09,0.00,N/A
|
||||||
|
172.67.79.153,4,1,0.75,205.89,0.00,N/A
|
||||||
|
104.25.177.29,4,1,0.75,206.47,0.00,N/A
|
||||||
|
104.23.127.84,4,1,0.75,206.79,0.00,N/A
|
||||||
|
172.65.157.26,4,1,0.75,206.81,0.00,N/A
|
||||||
|
172.65.143.100,4,1,0.75,207.04,0.00,N/A
|
||||||
|
172.67.213.100,4,1,0.75,207.71,0.00,N/A
|
||||||
|
172.67.120.9,4,1,0.75,207.72,0.00,N/A
|
||||||
|
172.67.153.129,4,1,0.75,208.63,0.00,N/A
|
||||||
|
172.67.125.200,4,1,0.75,208.65,0.00,N/A
|
||||||
|
104.25.149.106,4,1,0.75,208.83,0.00,N/A
|
||||||
|
162.159.34.226,4,1,0.75,209.01,0.00,N/A
|
||||||
|
104.21.104.184,4,1,0.75,209.12,0.00,N/A
|
||||||
|
172.67.212.137,4,1,0.75,209.67,0.00,N/A
|
||||||
|
172.67.123.136,4,1,0.75,209.89,0.00,N/A
|
||||||
|
104.21.0.77,4,1,0.75,210.27,0.00,N/A
|
||||||
|
172.65.170.217,4,1,0.75,210.29,0.00,N/A
|
||||||
|
104.27.35.43,4,1,0.75,210.34,0.00,N/A
|
||||||
|
104.27.79.195,4,1,0.75,210.51,0.00,N/A
|
||||||
|
104.27.63.69,4,1,0.75,210.53,0.00,N/A
|
||||||
|
104.25.26.185,4,1,0.75,210.61,0.00,N/A
|
||||||
|
104.25.42.149,4,1,0.75,210.70,0.00,N/A
|
||||||
|
198.41.196.53,4,1,0.75,210.92,0.00,N/A
|
||||||
|
172.65.202.128,4,1,0.75,211.10,0.00,N/A
|
||||||
|
104.17.17.118,4,1,0.75,211.16,0.00,N/A
|
||||||
|
104.25.126.214,4,1,0.75,211.18,0.00,N/A
|
||||||
|
172.65.123.189,4,1,0.75,211.24,0.00,N/A
|
||||||
|
104.27.60.48,4,1,0.75,211.46,0.00,N/A
|
||||||
|
172.65.186.1,4,1,0.75,211.52,0.00,N/A
|
||||||
|
172.65.86.79,4,1,0.75,211.57,0.00,N/A
|
||||||
|
104.21.10.53,4,1,0.75,211.76,0.00,N/A
|
||||||
|
172.65.184.152,4,1,0.75,211.78,0.00,N/A
|
||||||
|
104.27.70.234,4,1,0.75,211.98,0.00,N/A
|
||||||
|
172.67.168.42,4,1,0.75,212.00,0.00,N/A
|
||||||
|
104.21.99.173,4,1,0.75,212.05,0.00,N/A
|
||||||
|
172.65.181.172,4,1,0.75,212.07,0.00,N/A
|
||||||
|
172.67.203.54,4,1,0.75,212.08,0.00,N/A
|
||||||
|
104.27.50.143,4,1,0.75,212.10,0.00,N/A
|
||||||
|
104.21.53.192,4,1,0.75,212.10,0.00,N/A
|
||||||
|
172.67.206.8,4,1,0.75,212.26,0.00,N/A
|
||||||
|
104.27.77.89,4,1,0.75,212.28,0.00,N/A
|
||||||
|
104.25.151.104,4,1,0.75,212.32,0.00,N/A
|
||||||
|
104.21.12.16,4,1,0.75,212.68,0.00,N/A
|
||||||
|
104.21.17.104,4,1,0.75,212.70,0.00,N/A
|
||||||
|
172.65.187.74,4,1,0.75,212.74,0.00,N/A
|
||||||
|
104.25.33.231,4,1,0.75,212.80,0.00,N/A
|
||||||
|
104.25.141.117,4,1,0.75,213.00,0.00,N/A
|
||||||
|
104.27.67.150,4,1,0.75,213.13,0.00,N/A
|
||||||
|
172.65.144.178,4,1,0.75,213.20,0.00,N/A
|
||||||
|
104.21.52.226,4,1,0.75,213.68,0.00,N/A
|
||||||
|
172.65.177.1,4,1,0.75,213.78,0.00,N/A
|
||||||
|
104.27.58.213,4,1,0.75,214.07,0.00,N/A
|
||||||
|
172.67.178.33,4,1,0.75,214.39,0.00,N/A
|
||||||
|
172.67.127.189,4,1,0.75,214.40,0.00,N/A
|
||||||
|
172.67.167.87,4,1,0.75,214.48,0.00,N/A
|
||||||
|
104.21.96.216,4,1,0.75,214.66,0.00,N/A
|
||||||
|
104.21.103.195,4,1,0.75,214.83,0.00,N/A
|
||||||
|
104.23.122.11,4,1,0.75,214.85,0.00,N/A
|
||||||
|
104.21.87.122,4,1,0.75,214.92,0.00,N/A
|
||||||
|
172.67.185.245,4,1,0.75,214.92,0.00,N/A
|
||||||
|
172.65.91.153,4,1,0.75,215.02,0.00,N/A
|
||||||
|
104.27.97.215,4,1,0.75,215.05,0.00,N/A
|
||||||
|
104.25.142.8,4,1,0.75,215.25,0.00,N/A
|
||||||
|
104.23.132.217,4,1,0.75,215.33,0.00,N/A
|
||||||
|
172.67.145.128,4,1,0.75,215.37,0.00,N/A
|
||||||
|
172.67.210.240,4,1,0.75,215.43,0.00,N/A
|
||||||
|
172.67.182.141,4,1,0.75,215.57,0.00,N/A
|
||||||
|
172.67.170.100,4,1,0.75,215.85,0.00,N/A
|
||||||
|
172.67.215.56,4,1,0.75,216.24,0.00,N/A
|
||||||
|
172.65.176.224,4,1,0.75,216.76,0.00,N/A
|
||||||
|
172.67.151.13,4,1,0.75,217.05,0.00,N/A
|
||||||
|
104.21.198.22,4,1,0.75,217.27,0.00,N/A
|
||||||
|
104.21.50.44,4,1,0.75,217.36,0.00,N/A
|
||||||
|
104.25.32.177,4,1,0.75,217.38,0.00,N/A
|
||||||
|
104.19.35.200,4,1,0.75,217.44,0.00,N/A
|
||||||
|
104.21.115.123,4,1,0.75,217.72,0.00,N/A
|
||||||
|
104.21.20.237,4,1,0.75,218.20,0.00,N/A
|
||||||
|
172.67.173.238,4,1,0.75,218.40,0.00,N/A
|
||||||
|
104.25.231.57,4,1,0.75,218.45,0.00,N/A
|
||||||
|
104.21.106.152,4,1,0.75,218.58,0.00,N/A
|
||||||
|
104.27.126.205,4,1,0.75,218.88,0.00,N/A
|
||||||
|
172.67.124.177,4,1,0.75,219.90,0.00,N/A
|
||||||
|
172.66.164.0,4,1,0.75,220.22,0.00,N/A
|
||||||
|
104.21.121.201,4,1,0.75,221.67,0.00,N/A
|
||||||
|
104.19.45.187,4,1,0.75,221.99,0.00,N/A
|
||||||
|
104.21.6.226,4,1,0.75,222.78,0.00,N/A
|
||||||
|
172.67.188.195,4,1,0.75,224.44,0.00,N/A
|
||||||
|
104.21.59.151,4,1,0.75,224.92,0.00,N/A
|
||||||
|
104.21.111.210,4,1,0.75,225.97,0.00,N/A
|
||||||
|
172.65.221.120,4,1,0.75,226.62,0.00,N/A
|
||||||
|
172.65.93.123,4,1,0.75,226.94,0.00,N/A
|
||||||
|
104.21.82.137,4,1,0.75,227.02,0.00,N/A
|
||||||
|
104.21.75.155,4,1,0.75,227.19,0.00,N/A
|
||||||
|
104.21.4.103,4,1,0.75,227.65,0.00,N/A
|
||||||
|
172.65.218.115,4,1,0.75,228.33,0.00,N/A
|
||||||
|
172.67.183.190,4,1,0.75,228.38,0.00,N/A
|
||||||
|
172.65.225.135,4,1,0.75,228.57,0.00,N/A
|
||||||
|
104.21.18.142,4,1,0.75,228.62,0.00,N/A
|
||||||
|
172.67.216.164,4,1,0.75,229.22,0.00,N/A
|
||||||
|
104.21.41.229,4,1,0.75,229.28,0.00,N/A
|
||||||
|
104.21.86.97,4,1,0.75,229.30,0.00,N/A
|
||||||
|
104.21.54.37,4,1,0.75,229.37,0.00,N/A
|
||||||
|
172.67.190.171,4,1,0.75,229.48,0.00,N/A
|
||||||
|
172.67.194.139,4,1,0.75,229.59,0.00,N/A
|
||||||
|
104.21.40.93,4,1,0.75,229.68,0.00,N/A
|
||||||
|
172.67.180.110,4,1,0.75,229.94,0.00,N/A
|
||||||
|
172.67.166.204,4,1,0.75,230.28,0.00,N/A
|
||||||
|
104.21.74.128,4,1,0.75,230.60,0.00,N/A
|
||||||
|
172.67.214.107,4,1,0.75,230.77,0.00,N/A
|
||||||
|
172.67.175.89,4,1,0.75,231.01,0.00,N/A
|
||||||
|
104.16.175.228,4,1,0.75,231.75,0.00,N/A
|
||||||
|
104.21.48.8,4,1,0.75,232.07,0.00,N/A
|
||||||
|
104.21.37.104,4,1,0.75,232.16,0.00,N/A
|
||||||
|
104.21.8.192,4,1,0.75,233.78,0.00,N/A
|
||||||
|
172.67.202.238,4,1,0.75,235.09,0.00,N/A
|
||||||
|
172.67.205.247,4,1,0.75,235.42,0.00,N/A
|
||||||
|
172.65.223.201,4,1,0.75,236.56,0.00,N/A
|
||||||
|
104.21.193.204,4,1,0.75,236.93,0.00,N/A
|
||||||
|
104.21.102.58,4,1,0.75,236.95,0.00,N/A
|
||||||
|
104.18.231.140,4,1,0.75,238.04,0.00,N/A
|
||||||
|
172.67.154.197,4,1,0.75,240.34,0.00,N/A
|
||||||
|
104.16.180.108,4,1,0.75,242.44,0.00,N/A
|
||||||
|
104.21.116.58,4,1,0.75,243.03,0.00,N/A
|
||||||
|
172.67.136.221,4,1,0.75,243.68,0.00,N/A
|
||||||
|
104.21.13.66,4,1,0.75,244.41,0.00,N/A
|
||||||
|
104.21.92.240,4,1,0.75,244.88,0.00,N/A
|
||||||
|
172.67.157.171,4,1,0.75,245.27,0.00,N/A
|
||||||
|
104.21.101.247,4,1,0.75,246.05,0.00,N/A
|
||||||
|
172.67.181.79,4,1,0.75,246.05,0.00,N/A
|
||||||
|
104.21.118.139,4,1,0.75,246.36,0.00,N/A
|
||||||
|
172.67.177.56,4,1,0.75,246.65,0.00,N/A
|
||||||
|
104.21.127.178,4,1,0.75,246.81,0.00,N/A
|
||||||
|
104.21.98.191,4,1,0.75,247.32,0.00,N/A
|
||||||
|
104.21.113.193,4,1,0.75,247.69,0.00,N/A
|
||||||
|
172.67.189.86,4,1,0.75,248.51,0.00,N/A
|
||||||
|
104.21.2.249,4,1,0.75,248.68,0.00,N/A
|
||||||
|
104.21.222.98,4,1,0.75,248.68,0.00,N/A
|
||||||
|
104.18.242.144,4,1,0.75,250.21,0.00,N/A
|
||||||
|
104.21.126.207,4,1,0.75,251.52,0.00,N/A
|
||||||
|
104.16.242.230,4,1,0.75,253.13,0.00,N/A
|
||||||
|
172.67.211.106,4,1,0.75,253.30,0.00,N/A
|
||||||
|
104.21.114.153,4,1,0.75,256.73,0.00,N/A
|
||||||
|
104.21.97.15,4,1,0.75,258.68,0.00,N/A
|
||||||
|
172.67.174.227,4,1,0.75,259.79,0.00,N/A
|
||||||
|
172.67.198.73,4,1,0.75,259.83,0.00,N/A
|
||||||
|
104.19.3.200,4,1,0.75,260.44,0.00,N/A
|
||||||
|
104.19.63.44,4,1,0.75,262.87,0.00,N/A
|
||||||
|
172.65.32.207,4,1,0.75,263.57,0.00,N/A
|
||||||
|
104.18.216.81,4,1,0.75,264.24,0.00,N/A
|
||||||
|
104.21.1.68,4,1,0.75,265.42,0.00,N/A
|
||||||
|
104.21.81.31,4,1,0.75,265.49,0.00,N/A
|
||||||
|
104.18.251.232,4,1,0.75,266.91,0.00,N/A
|
||||||
|
172.67.197.65,4,1,0.75,267.28,0.00,N/A
|
||||||
|
104.18.156.241,4,1,0.75,269.81,0.00,N/A
|
||||||
|
172.65.24.244,4,1,0.75,271.18,0.00,N/A
|
||||||
|
104.16.253.250,4,1,0.75,271.66,0.00,N/A
|
||||||
|
104.17.4.252,4,1,0.75,273.42,0.00,N/A
|
||||||
|
104.18.215.115,4,1,0.75,278.16,0.00,N/A
|
||||||
|
104.16.212.149,4,1,0.75,287.06,0.00,N/A
|
||||||
|
104.17.13.0,4,1,0.75,290.13,0.00,N/A
|
||||||
|
141.101.114.149,4,1,0.75,303.88,0.00,N/A
|
||||||
|
104.18.230.149,4,1,0.75,333.73,0.00,N/A
|
||||||
|
104.18.248.2,4,1,0.75,346.67,0.00,N/A
|
||||||
|
104.18.71.193,4,1,0.75,358.68,0.00,N/A
|
||||||
|
104.17.2.17,4,1,0.75,386.55,0.00,N/A
|
||||||
|
104.17.216.244,4,1,0.75,398.34,0.00,N/A
|
||||||
|
11
source/bin/result.csv
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
IP 地址,已发送,已接收,丢包率,平均延迟,下载速度(MB/s),地区码
|
||||||
|
104.21.237.213,4,4,0.00,173.18,86.14,LAX
|
||||||
|
141.101.120.105,4,4,0.00,173.02,40.72,SJC
|
||||||
|
162.159.251.244,4,4,0.00,171.02,8.59,LAX
|
||||||
|
104.18.19.226,4,4,0.00,172.26,5.27,LAX
|
||||||
|
104.19.32.102,4,4,0.00,173.01,4.52,SJC
|
||||||
|
141.101.121.113,4,4,0.00,169.51,3.69,SJC
|
||||||
|
162.159.207.149,4,4,0.00,169.94,3.24,SJC
|
||||||
|
104.19.21.114,4,4,0.00,171.00,0.38,SJC
|
||||||
|
104.19.127.210,4,4,0.00,170.53,0.22,SJC
|
||||||
|
104.19.60.94,4,4,0.00,168.33,0.00,N/A
|
||||||
|
11
source/core/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from .animations import MultiStageAnimations
|
||||||
|
from .ui_manager import UIManager
|
||||||
|
from .download_manager import DownloadManager
|
||||||
|
from .debug_manager import DebugManager
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'MultiStageAnimations',
|
||||||
|
'UIManager',
|
||||||
|
'DownloadManager',
|
||||||
|
'DebugManager'
|
||||||
|
]
|
||||||
57
source/core/debug_manager.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from PySide6 import QtWidgets
|
||||||
|
from data.config import LOG_FILE
|
||||||
|
from utils import Logger
|
||||||
|
|
||||||
|
class DebugManager:
|
||||||
|
def __init__(self, main_window):
|
||||||
|
"""初始化调试管理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
main_window: 主窗口实例
|
||||||
|
"""
|
||||||
|
self.main_window = main_window
|
||||||
|
self.logger = None
|
||||||
|
self.original_stdout = None
|
||||||
|
self.original_stderr = None
|
||||||
|
|
||||||
|
def toggle_debug_mode(self, checked):
|
||||||
|
"""切换调试模式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
checked: 是否启用调试模式
|
||||||
|
"""
|
||||||
|
self.main_window.config["debug_mode"] = checked
|
||||||
|
self.main_window.save_config(self.main_window.config)
|
||||||
|
if checked:
|
||||||
|
self.start_logging()
|
||||||
|
else:
|
||||||
|
self.stop_logging()
|
||||||
|
|
||||||
|
def start_logging(self):
|
||||||
|
"""启动日志记录"""
|
||||||
|
if self.logger is None:
|
||||||
|
try:
|
||||||
|
if os.path.exists(LOG_FILE):
|
||||||
|
os.remove(LOG_FILE)
|
||||||
|
# 保存原始的 stdout 和 stderr
|
||||||
|
self.original_stdout = sys.stdout
|
||||||
|
self.original_stderr = sys.stderr
|
||||||
|
# 创建 Logger 实例
|
||||||
|
self.logger = Logger(LOG_FILE, self.original_stdout)
|
||||||
|
sys.stdout = self.logger
|
||||||
|
sys.stderr = self.logger
|
||||||
|
print("--- Debug mode enabled ---")
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
QtWidgets.QMessageBox.critical(self.main_window, "错误", f"无法创建日志文件: {e}")
|
||||||
|
self.logger = None
|
||||||
|
|
||||||
|
def stop_logging(self):
|
||||||
|
"""停止日志记录"""
|
||||||
|
if self.logger:
|
||||||
|
print("--- Debug mode disabled ---")
|
||||||
|
sys.stdout = self.original_stdout
|
||||||
|
sys.stderr = self.original_stderr
|
||||||
|
self.logger.close()
|
||||||
|
self.logger = None
|
||||||
493
source/core/download_manager.py
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from collections import deque
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from PySide6 import QtWidgets
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
|
||||||
|
from utils import msgbox_frame, HostsManager
|
||||||
|
from data.config import APP_NAME, PLUGIN, GAME_INFO, UA, CONFIG_URL
|
||||||
|
from workers import IpOptimizerThread
|
||||||
|
|
||||||
|
class DownloadManager:
|
||||||
|
def __init__(self, main_window):
|
||||||
|
"""初始化下载管理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
main_window: 主窗口实例,用于访问UI和状态
|
||||||
|
"""
|
||||||
|
self.main_window = main_window
|
||||||
|
self.selected_folder = ""
|
||||||
|
self.download_queue = deque()
|
||||||
|
self.current_download_thread = None
|
||||||
|
self.hosts_manager = HostsManager()
|
||||||
|
self.optimized_ip = None
|
||||||
|
self.optimization_done = False # 标记是否已执行过优选
|
||||||
|
self.optimizing_msg_box = None
|
||||||
|
|
||||||
|
def file_dialog(self):
|
||||||
|
"""显示文件夹选择对话框,选择游戏安装目录"""
|
||||||
|
self.selected_folder = QtWidgets.QFileDialog.getExistingDirectory(
|
||||||
|
self.main_window, f"选择游戏所在【上级目录】 {APP_NAME}"
|
||||||
|
)
|
||||||
|
if not self.selected_folder:
|
||||||
|
QtWidgets.QMessageBox.warning(
|
||||||
|
self.main_window, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self.download_action()
|
||||||
|
|
||||||
|
def get_install_paths(self):
|
||||||
|
"""获取所有游戏版本的安装路径"""
|
||||||
|
return {
|
||||||
|
game: os.path.join(self.selected_folder, info["install_path"])
|
||||||
|
for game, info in GAME_INFO.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def is_debug_mode(self):
|
||||||
|
"""检查是否处于调试模式"""
|
||||||
|
if hasattr(self.main_window, 'ui_manager') and self.main_window.ui_manager:
|
||||||
|
if hasattr(self.main_window.ui_manager, 'debug_action') and self.main_window.ui_manager.debug_action:
|
||||||
|
return self.main_window.ui_manager.debug_action.isChecked()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_download_url(self) -> dict:
|
||||||
|
"""获取所有游戏版本的下载链接
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含游戏版本和下载URL的字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.main_window.cloud_config:
|
||||||
|
if self.is_debug_mode():
|
||||||
|
print("--- Using pre-fetched cloud config ---")
|
||||||
|
config_data = self.main_window.cloud_config
|
||||||
|
else:
|
||||||
|
# 如果没有预加载的配置,则同步获取
|
||||||
|
headers = {"User-Agent": UA}
|
||||||
|
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
config_data = response.json()
|
||||||
|
|
||||||
|
if not config_data:
|
||||||
|
raise ValueError("未能获取或解析配置数据")
|
||||||
|
|
||||||
|
if self.is_debug_mode():
|
||||||
|
print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}")
|
||||||
|
|
||||||
|
# 统一处理URL提取,确保返回扁平化的字典
|
||||||
|
urls = {}
|
||||||
|
for i in range(4):
|
||||||
|
key = f"vol.{i+1}.data"
|
||||||
|
if key in config_data and "url" in config_data[key]:
|
||||||
|
urls[f"vol{i+1}"] = config_data[key]["url"]
|
||||||
|
|
||||||
|
if "after.data" in config_data and "url" in config_data["after.data"]:
|
||||||
|
urls["after"] = config_data["after.data"]["url"]
|
||||||
|
|
||||||
|
# 检查是否成功提取了所有URL
|
||||||
|
if len(urls) != 5:
|
||||||
|
missing_keys_map = {
|
||||||
|
f"vol{i+1}": f"vol.{i+1}.data" for i in range(4)
|
||||||
|
}
|
||||||
|
missing_keys_map["after"] = "after.data"
|
||||||
|
|
||||||
|
extracted_keys = set(urls.keys())
|
||||||
|
all_keys = set(missing_keys_map.keys())
|
||||||
|
missing_simple_keys = all_keys - extracted_keys
|
||||||
|
|
||||||
|
missing_original_keys = [missing_keys_map[k] for k in missing_simple_keys]
|
||||||
|
raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_original_keys)}")
|
||||||
|
|
||||||
|
if self.is_debug_mode():
|
||||||
|
print(f"DEBUG: Extracted URLs: {urls}")
|
||||||
|
print("--- Finished getting download URL successfully ---")
|
||||||
|
return urls
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
status_code = e.response.status_code if e.response is not None else "未知"
|
||||||
|
try:
|
||||||
|
error_response = e.response.json() if e.response else {}
|
||||||
|
json_title = error_response.get("title", "无错误类型")
|
||||||
|
json_message = error_response.get("message", "无附加错误信息")
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
json_title = "配置文件异常,无法解析错误类型"
|
||||||
|
json_message = "配置文件异常,无法解析错误信息"
|
||||||
|
|
||||||
|
if self.is_debug_mode():
|
||||||
|
print(f"ERROR: Failed to get download config due to RequestException: {e}")
|
||||||
|
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self.main_window,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
f"\n下载配置获取失败\n\n【HTTP状态】:{status_code}\n【错误类型】:{json_title}\n【错误信息】:{json_message}\n",
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
except ValueError as e:
|
||||||
|
if self.is_debug_mode():
|
||||||
|
print(f"ERROR: Failed to parse download config due to ValueError: {e}")
|
||||||
|
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self.main_window,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
f"\n配置文件格式异常\n\n【错误信息】:{e}\n",
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def download_action(self):
|
||||||
|
"""开始下载流程"""
|
||||||
|
# 禁用开始安装按钮
|
||||||
|
self.main_window.ui.start_install_btn.setEnabled(False)
|
||||||
|
|
||||||
|
# 显示哈希检查窗口
|
||||||
|
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window()
|
||||||
|
|
||||||
|
# 执行预检查
|
||||||
|
install_paths = self.get_install_paths()
|
||||||
|
|
||||||
|
self.main_window.hash_thread = self.main_window.create_hash_thread("pre", install_paths)
|
||||||
|
self.main_window.hash_thread.pre_finished.connect(self.on_pre_hash_finished)
|
||||||
|
self.main_window.hash_thread.start()
|
||||||
|
|
||||||
|
def on_pre_hash_finished(self, updated_status):
|
||||||
|
"""哈希预检查完成后的处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
updated_status: 更新后的安装状态
|
||||||
|
"""
|
||||||
|
self.main_window.installed_status = updated_status
|
||||||
|
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
||||||
|
self.main_window.hash_msg_box.accept()
|
||||||
|
self.main_window.hash_msg_box = None
|
||||||
|
|
||||||
|
# 获取下载配置
|
||||||
|
config = self.get_download_url()
|
||||||
|
if not config:
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self.main_window, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||||||
|
)
|
||||||
|
# 重新启用开始安装按钮
|
||||||
|
self.main_window.ui.start_install_btn.setEnabled(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 填充下载队列
|
||||||
|
self._fill_download_queue(config)
|
||||||
|
|
||||||
|
# 如果没有需要下载的内容,直接进行最终校验
|
||||||
|
if not self.download_queue:
|
||||||
|
self.main_window.after_hash_compare()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 只有当有需要下载内容时才询问是否使用Cloudflare加速
|
||||||
|
# 询问用户是否使用Cloudflare加速
|
||||||
|
msg_box = QtWidgets.QMessageBox(self.main_window)
|
||||||
|
msg_box.setWindowTitle(f"下载优化 - {APP_NAME}")
|
||||||
|
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。")
|
||||||
|
msg_box.setIcon(QtWidgets.QMessageBox.Icon.Question)
|
||||||
|
|
||||||
|
yes_button = msg_box.addButton("是,开启加速", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||||||
|
no_button = msg_box.addButton("否,直接下载", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||||||
|
|
||||||
|
msg_box.exec()
|
||||||
|
|
||||||
|
use_optimization = msg_box.clickedButton() == yes_button
|
||||||
|
|
||||||
|
if use_optimization and not self.optimization_done:
|
||||||
|
first_url = self.download_queue[0][0]
|
||||||
|
self._start_ip_optimization(first_url)
|
||||||
|
else:
|
||||||
|
# 如果用户选择不优化,或已经优化过,直接开始下载
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
def _fill_download_queue(self, config):
|
||||||
|
"""填充下载队列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 包含下载URL的配置字典
|
||||||
|
"""
|
||||||
|
# 清空现有队列
|
||||||
|
self.download_queue.clear()
|
||||||
|
|
||||||
|
# 添加nekopara 1-4
|
||||||
|
for i in range(1, 5):
|
||||||
|
game_version = f"NEKOPARA Vol.{i}"
|
||||||
|
if not self.main_window.installed_status.get(game_version, False):
|
||||||
|
url = config.get(f"vol{i}")
|
||||||
|
if not url: continue
|
||||||
|
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
|
||||||
|
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
||||||
|
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||||
|
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||||
|
|
||||||
|
# 添加nekopara after
|
||||||
|
game_version = "NEKOPARA After"
|
||||||
|
if not self.main_window.installed_status.get(game_version, False):
|
||||||
|
url = config.get("after")
|
||||||
|
if url:
|
||||||
|
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
|
||||||
|
_7z_path = os.path.join(PLUGIN, "after.7z")
|
||||||
|
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||||
|
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||||
|
|
||||||
|
def _start_ip_optimization(self, url):
|
||||||
|
"""开始IP优化过程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: 用于优化的URL
|
||||||
|
"""
|
||||||
|
# 禁用退出按钮
|
||||||
|
self.main_window.ui.exit_btn.setEnabled(False)
|
||||||
|
|
||||||
|
self.optimizing_msg_box = msgbox_frame(
|
||||||
|
f"通知 - {APP_NAME}",
|
||||||
|
"\n正在优选Cloudflare IP,请稍候...\n\n这可能需要5-10分钟,请耐心等待喵~"
|
||||||
|
)
|
||||||
|
# 我们不再提供"跳过"按钮
|
||||||
|
self.optimizing_msg_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.NoButton)
|
||||||
|
self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
self.optimizing_msg_box.open()
|
||||||
|
|
||||||
|
# 创建并启动优化线程
|
||||||
|
self.ip_optimizer_thread = IpOptimizerThread(url)
|
||||||
|
self.ip_optimizer_thread.finished.connect(self.on_optimization_finished)
|
||||||
|
self.ip_optimizer_thread.start()
|
||||||
|
|
||||||
|
def on_optimization_finished(self, ip):
|
||||||
|
"""IP优化完成后的处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ip: 优选的IP地址,如果失败则为空字符串
|
||||||
|
"""
|
||||||
|
self.optimized_ip = ip
|
||||||
|
self.optimization_done = True
|
||||||
|
|
||||||
|
# 关闭提示框
|
||||||
|
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
||||||
|
if self.optimizing_msg_box.isVisible():
|
||||||
|
self.optimizing_msg_box.accept()
|
||||||
|
self.optimizing_msg_box = None
|
||||||
|
|
||||||
|
# 显示优选结果
|
||||||
|
if not ip:
|
||||||
|
QtWidgets.QMessageBox.warning(
|
||||||
|
self.main_window,
|
||||||
|
f"优选失败 - {APP_NAME}",
|
||||||
|
"\n未能找到合适的Cloudflare IP,将使用默认网络进行下载。\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 应用优选IP到hosts文件
|
||||||
|
if self.download_queue:
|
||||||
|
first_url = self.download_queue[0][0]
|
||||||
|
hostname = urlparse(first_url).hostname
|
||||||
|
|
||||||
|
# 先清理可能存在的旧记录
|
||||||
|
self.hosts_manager.clean_hostname_entries(hostname)
|
||||||
|
|
||||||
|
if self.hosts_manager.apply_ip(hostname, ip):
|
||||||
|
QtWidgets.QMessageBox.information(
|
||||||
|
self.main_window,
|
||||||
|
f"成功 - {APP_NAME}",
|
||||||
|
f"\n已将优选IP ({ip}) 应用到hosts文件。\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self.main_window,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
"\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 开始下载
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
def next_download_task(self):
|
||||||
|
"""处理下载队列中的下一个任务"""
|
||||||
|
if not self.download_queue:
|
||||||
|
self.main_window.after_hash_compare()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务
|
||||||
|
if self.current_download_thread and self.current_download_thread.isRunning():
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取下一个下载任务并开始
|
||||||
|
url, game_folder, game_version, _7z_path, plugin_path = self.download_queue.popleft()
|
||||||
|
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||||
|
|
||||||
|
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
||||||
|
"""准备下载特定游戏版本
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: 下载URL
|
||||||
|
game_folder: 游戏文件夹路径
|
||||||
|
game_version: 游戏版本名称
|
||||||
|
_7z_path: 7z文件保存路径
|
||||||
|
plugin_path: 插件路径
|
||||||
|
"""
|
||||||
|
game_exe = {
|
||||||
|
game: os.path.join(
|
||||||
|
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
|
||||||
|
)
|
||||||
|
for game, info in GAME_INFO.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查游戏是否已安装
|
||||||
|
if (
|
||||||
|
game_version not in game_exe
|
||||||
|
or not os.path.exists(game_exe[game_version])
|
||||||
|
or self.main_window.installed_status[game_version]
|
||||||
|
):
|
||||||
|
self.main_window.installed_status[game_version] = False
|
||||||
|
self.main_window.show_result()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建进度窗口并开始下载
|
||||||
|
self.main_window.progress_window = self.main_window.create_progress_window()
|
||||||
|
self.start_download(url, _7z_path, game_version, game_folder, plugin_path)
|
||||||
|
|
||||||
|
def start_download(self, url, _7z_path, game_version, game_folder, plugin_path):
|
||||||
|
"""启动下载线程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: 下载URL
|
||||||
|
_7z_path: 7z文件保存路径
|
||||||
|
game_version: 游戏版本名称
|
||||||
|
game_folder: 游戏文件夹路径
|
||||||
|
plugin_path: 插件路径
|
||||||
|
"""
|
||||||
|
# 禁用退出按钮
|
||||||
|
self.main_window.ui.exit_btn.setEnabled(False)
|
||||||
|
|
||||||
|
if self.optimized_ip:
|
||||||
|
print(f"已为 {game_version} 获取到优选IP: {self.optimized_ip}")
|
||||||
|
else:
|
||||||
|
print(f"未能为 {game_version} 获取优选IP,将使用默认线路。")
|
||||||
|
|
||||||
|
# 创建并连接下载线程
|
||||||
|
self.current_download_thread = self.main_window.create_download_thread(url, _7z_path, game_version)
|
||||||
|
self.current_download_thread.progress.connect(self.main_window.progress_window.update_progress)
|
||||||
|
self.current_download_thread.finished.connect(
|
||||||
|
lambda success, error: self.on_download_finished(
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
url,
|
||||||
|
game_folder,
|
||||||
|
game_version,
|
||||||
|
_7z_path,
|
||||||
|
plugin_path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 连接停止按钮
|
||||||
|
self.main_window.progress_window.stop_button.clicked.connect(self.current_download_thread.stop)
|
||||||
|
|
||||||
|
# 启动线程和显示进度窗口
|
||||||
|
self.current_download_thread.start()
|
||||||
|
self.main_window.progress_window.exec()
|
||||||
|
|
||||||
|
def on_download_finished(self, success, error, url, game_folder, game_version, _7z_path, plugin_path):
|
||||||
|
"""下载完成后的处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
success: 是否下载成功
|
||||||
|
error: 错误信息
|
||||||
|
url: 下载URL
|
||||||
|
game_folder: 游戏文件夹路径
|
||||||
|
game_version: 游戏版本名称
|
||||||
|
_7z_path: 7z文件保存路径
|
||||||
|
plugin_path: 插件路径
|
||||||
|
"""
|
||||||
|
# 关闭进度窗口
|
||||||
|
if self.main_window.progress_window.isVisible():
|
||||||
|
self.main_window.progress_window.reject()
|
||||||
|
|
||||||
|
# 处理下载失败
|
||||||
|
if not success:
|
||||||
|
print(f"--- Download Failed: {game_version} ---")
|
||||||
|
print(error)
|
||||||
|
print("------------------------------------")
|
||||||
|
msg_box = QtWidgets.QMessageBox(self.main_window)
|
||||||
|
msg_box.setWindowTitle(f"下载失败 - {APP_NAME}")
|
||||||
|
msg_box.setText(f"\n文件获取失败: {game_version}\n错误: {error}\n\n是否重试?")
|
||||||
|
|
||||||
|
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||||||
|
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||||||
|
end_button = msg_box.addButton("结束", QtWidgets.QMessageBox.ButtonRole.RejectRole)
|
||||||
|
|
||||||
|
msg_box.exec()
|
||||||
|
clicked_button = msg_box.clickedButton()
|
||||||
|
|
||||||
|
# 处理用户选择
|
||||||
|
if clicked_button == retry_button:
|
||||||
|
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||||
|
elif clicked_button == next_button:
|
||||||
|
self.next_download_task()
|
||||||
|
else:
|
||||||
|
self.on_download_stopped()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 下载成功,开始解压缩
|
||||||
|
self.main_window.hash_msg_box = self.main_window.hash_manager.hash_pop_window()
|
||||||
|
|
||||||
|
# 创建并启动解压线程
|
||||||
|
self.main_window.extraction_thread = self.main_window.create_extraction_thread(
|
||||||
|
_7z_path, game_folder, plugin_path, game_version
|
||||||
|
)
|
||||||
|
self.main_window.extraction_thread.finished.connect(self.on_extraction_finished)
|
||||||
|
self.main_window.extraction_thread.start()
|
||||||
|
|
||||||
|
def on_extraction_finished(self, success, error_message, game_version):
|
||||||
|
"""解压完成后的处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
success: 是否解压成功
|
||||||
|
error_message: 错误信息
|
||||||
|
game_version: 游戏版本
|
||||||
|
"""
|
||||||
|
# 关闭哈希检查窗口
|
||||||
|
if self.main_window.hash_msg_box and self.main_window.hash_msg_box.isVisible():
|
||||||
|
self.main_window.hash_msg_box.close()
|
||||||
|
|
||||||
|
# 处理解压结果
|
||||||
|
if not success:
|
||||||
|
QtWidgets.QMessageBox.critical(self.main_window, f"错误 - {APP_NAME}", error_message)
|
||||||
|
self.main_window.installed_status[game_version] = False
|
||||||
|
else:
|
||||||
|
self.main_window.installed_status[game_version] = True
|
||||||
|
|
||||||
|
# 继续下一个下载任务
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
def on_download_stopped(self):
|
||||||
|
"""当用户点击停止按钮或选择结束时调用的函数"""
|
||||||
|
# 停止IP优化线程
|
||||||
|
if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning():
|
||||||
|
self.ip_optimizer_thread.stop()
|
||||||
|
self.ip_optimizer_thread.wait()
|
||||||
|
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
||||||
|
if self.optimizing_msg_box.isVisible():
|
||||||
|
self.optimizing_msg_box.accept()
|
||||||
|
self.optimizing_msg_box = None
|
||||||
|
|
||||||
|
# 停止当前可能仍在运行的下载线程
|
||||||
|
if self.current_download_thread and self.current_download_thread.isRunning():
|
||||||
|
self.current_download_thread.stop()
|
||||||
|
self.current_download_thread.wait() # 等待线程完全终止
|
||||||
|
|
||||||
|
# 清空下载队列,因为用户决定停止
|
||||||
|
self.download_queue.clear()
|
||||||
|
|
||||||
|
# 确保进度窗口已关闭
|
||||||
|
if hasattr(self.main_window, 'progress_window') and self.main_window.progress_window.isVisible():
|
||||||
|
self.main_window.progress_window.reject()
|
||||||
|
|
||||||
|
# 可以在这里决定是否立即进行哈希比较或显示结果
|
||||||
|
print("下载已全部停止。")
|
||||||
|
self.main_window.setEnabled(True) # 恢复主窗口交互
|
||||||
|
|
||||||
|
# 重新启用退出按钮和开始安装按钮
|
||||||
|
self.main_window.ui.exit_btn.setEnabled(True)
|
||||||
|
self.main_window.ui.start_install_btn.setEnabled(True)
|
||||||
|
|
||||||
|
self.main_window.show_result()
|
||||||
94
source/core/ui_manager.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from PySide6.QtGui import QIcon, QAction
|
||||||
|
from PySide6.QtWidgets import QMessageBox, QMainWindow
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
from utils import load_base64_image, msgbox_frame
|
||||||
|
from data.config import APP_NAME, APP_VERSION
|
||||||
|
from data.pic_data import img_data
|
||||||
|
|
||||||
|
class UIManager:
|
||||||
|
def __init__(self, main_window):
|
||||||
|
"""初始化UI管理器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
main_window: 主窗口实例,用于设置UI元素
|
||||||
|
"""
|
||||||
|
self.main_window = main_window
|
||||||
|
# 使用getattr获取ui属性,如果不存在则为None
|
||||||
|
self.ui = getattr(main_window, 'ui', None)
|
||||||
|
self.debug_action = None
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
"""设置UI元素,包括窗口图标、标题和菜单"""
|
||||||
|
# 设置窗口图标
|
||||||
|
icon_data = img_data.get("icon")
|
||||||
|
if icon_data:
|
||||||
|
pixmap = load_base64_image(icon_data)
|
||||||
|
self.main_window.setWindowIcon(QIcon(pixmap))
|
||||||
|
|
||||||
|
# 设置窗口标题
|
||||||
|
self.main_window.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
||||||
|
|
||||||
|
# 设置菜单
|
||||||
|
self._setup_help_menu()
|
||||||
|
self._setup_settings_menu()
|
||||||
|
|
||||||
|
def _setup_help_menu(self):
|
||||||
|
"""设置"帮助"菜单"""
|
||||||
|
if not self.ui or not hasattr(self.ui, 'menu_2'):
|
||||||
|
return
|
||||||
|
|
||||||
|
project_home_action = QAction("项目主页", self.main_window)
|
||||||
|
project_home_action.triggered.connect(self.open_project_home_page)
|
||||||
|
|
||||||
|
about_action = QAction("关于", self.main_window)
|
||||||
|
about_action.triggered.connect(self.show_about_dialog)
|
||||||
|
|
||||||
|
self.ui.menu_2.addAction(project_home_action)
|
||||||
|
self.ui.menu_2.addAction(about_action)
|
||||||
|
|
||||||
|
def _setup_settings_menu(self):
|
||||||
|
"""设置"设置"菜单"""
|
||||||
|
if not self.ui or not hasattr(self.ui, 'menu'):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.debug_action = QAction("Debug模式", self.main_window, checkable=True)
|
||||||
|
|
||||||
|
# 安全地获取config属性
|
||||||
|
config = getattr(self.main_window, 'config', {})
|
||||||
|
debug_mode = False
|
||||||
|
if isinstance(config, dict):
|
||||||
|
debug_mode = config.get("debug_mode", False)
|
||||||
|
|
||||||
|
self.debug_action.setChecked(debug_mode)
|
||||||
|
|
||||||
|
# 安全地连接toggle_debug_mode方法
|
||||||
|
if hasattr(self.main_window, 'toggle_debug_mode'):
|
||||||
|
self.debug_action.triggered.connect(self.main_window.toggle_debug_mode)
|
||||||
|
|
||||||
|
self.ui.menu.addAction(self.debug_action)
|
||||||
|
|
||||||
|
# 为未来功能预留的"切换下载源"按钮
|
||||||
|
self.switch_source_action = QAction("切换下载源", self.main_window)
|
||||||
|
self.switch_source_action.setEnabled(False) # 暂时禁用
|
||||||
|
self.ui.menu.addAction(self.switch_source_action)
|
||||||
|
|
||||||
|
def open_project_home_page(self):
|
||||||
|
"""打开项目主页"""
|
||||||
|
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")
|
||||||
|
|
||||||
|
def show_about_dialog(self):
|
||||||
|
"""显示关于对话框"""
|
||||||
|
about_text = f"""
|
||||||
|
<p><b>{APP_NAME} v{APP_VERSION}</b></p>
|
||||||
|
<p>原作: <a href="https://github.com/Yanam1Anna">Yanam1Anna</a></p>
|
||||||
|
<p>此应用根据 <a href="https://github.com/hyb-oyqq/FRAISEMOE2-Installer/blob/master/LICENSE">GPL-3.0 许可证</a> 授权。</p>
|
||||||
|
"""
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"关于 - {APP_NAME}",
|
||||||
|
about_text,
|
||||||
|
QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.setTextFormat(Qt.TextFormat.RichText) # 使用Qt.TextFormat
|
||||||
|
msg_box.exec()
|
||||||
@@ -1,167 +1,27 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import webbrowser
|
|
||||||
import requests
|
|
||||||
import py7zr
|
|
||||||
import json
|
import json
|
||||||
from urllib.parse import urlparse
|
|
||||||
from collections import deque
|
|
||||||
from PySide6 import QtWidgets
|
|
||||||
from PySide6.QtCore import QTimer, Qt, QThread, Signal
|
|
||||||
from PySide6.QtGui import QIcon, QAction
|
|
||||||
from PySide6.QtWidgets import QMainWindow, QFileDialog, QApplication, QMessageBox, QPushButton
|
|
||||||
|
|
||||||
from Ui_install import Ui_MainWindows
|
from PySide6 import QtWidgets
|
||||||
from animations import MultiStageAnimations
|
from PySide6.QtCore import QTimer
|
||||||
from config import (
|
from PySide6.QtWidgets import QMainWindow, QMessageBox
|
||||||
APP_NAME, APP_VERSION, PLUGIN, GAME_INFO, BLOCK_SIZE,
|
|
||||||
|
from ui.Ui_install import Ui_MainWindows
|
||||||
|
from data.config import (
|
||||||
|
APP_NAME, PLUGIN, GAME_INFO, BLOCK_SIZE,
|
||||||
PLUGIN_HASH, UA, CONFIG_URL, LOG_FILE
|
PLUGIN_HASH, UA, CONFIG_URL, LOG_FILE
|
||||||
)
|
)
|
||||||
from utils import (
|
from utils import (
|
||||||
load_base64_image, HashManager, AdminPrivileges, msgbox_frame,
|
load_config, save_config, HashManager, AdminPrivileges, msgbox_frame
|
||||||
load_config, save_config, HostsManager, censor_url
|
)
|
||||||
|
from workers import (
|
||||||
|
DownloadThread, ProgressWindow, IpOptimizerThread,
|
||||||
|
HashThread, ExtractionThread, ConfigFetchThread
|
||||||
|
)
|
||||||
|
from core import (
|
||||||
|
MultiStageAnimations, UIManager, DownloadManager, DebugManager
|
||||||
)
|
)
|
||||||
from download import DownloadThread, ProgressWindow
|
|
||||||
from ip_optimizer import IpOptimizer
|
|
||||||
from pic_data import img_data
|
|
||||||
|
|
||||||
|
|
||||||
class Logger:
|
|
||||||
def __init__(self, filename, stream):
|
|
||||||
self.terminal = stream
|
|
||||||
self.log = open(filename, "w", encoding="utf-8")
|
|
||||||
|
|
||||||
def write(self, message):
|
|
||||||
censored_message = censor_url(message)
|
|
||||||
self.terminal.write(censored_message)
|
|
||||||
self.log.write(censored_message)
|
|
||||||
self.flush()
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
self.terminal.flush()
|
|
||||||
self.log.flush()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.log.close()
|
|
||||||
|
|
||||||
class IpOptimizerThread(QThread):
|
|
||||||
finished = Signal(str)
|
|
||||||
|
|
||||||
def __init__(self, url, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.url = url
|
|
||||||
self.optimizer = IpOptimizer()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
optimal_ip = self.optimizer.get_optimal_ip(self.url)
|
|
||||||
self.finished.emit(optimal_ip if optimal_ip else "")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.optimizer.stop()
|
|
||||||
|
|
||||||
class HashThread(QThread):
|
|
||||||
pre_finished = Signal(dict)
|
|
||||||
after_finished = Signal(dict)
|
|
||||||
|
|
||||||
def __init__(self, mode, install_paths, plugin_hash, installed_status, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.mode = mode
|
|
||||||
self.install_paths = install_paths
|
|
||||||
self.plugin_hash = plugin_hash
|
|
||||||
self.installed_status = installed_status
|
|
||||||
# 每个线程都应该有自己的HashManager实例
|
|
||||||
self.hash_manager = HashManager(BLOCK_SIZE)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if self.mode == "pre":
|
|
||||||
updated_status = self.hash_manager.cfg_pre_hash_compare(
|
|
||||||
self.install_paths, self.plugin_hash, self.installed_status
|
|
||||||
)
|
|
||||||
self.pre_finished.emit(updated_status)
|
|
||||||
elif self.mode == "after":
|
|
||||||
result = self.hash_manager.cfg_after_hash_compare(
|
|
||||||
self.install_paths, self.plugin_hash, self.installed_status
|
|
||||||
)
|
|
||||||
self.after_finished.emit(result)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractionThread(QThread):
|
|
||||||
finished = Signal(bool, str, str) # success, error_message, game_version
|
|
||||||
|
|
||||||
def __init__(self, _7z_path, game_folder, plugin_path, game_version, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self._7z_path = _7z_path
|
|
||||||
self.game_folder = game_folder
|
|
||||||
self.plugin_path = plugin_path
|
|
||||||
self.game_version = game_version
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
with py7zr.SevenZipFile(self._7z_path, mode="r") as archive:
|
|
||||||
archive.extractall(path=PLUGIN)
|
|
||||||
|
|
||||||
os.makedirs(self.game_folder, exist_ok=True)
|
|
||||||
shutil.copy(self.plugin_path, self.game_folder)
|
|
||||||
|
|
||||||
if self.game_version == "NEKOPARA After":
|
|
||||||
sig_path = os.path.join(PLUGIN, GAME_INFO[self.game_version]["sig_path"])
|
|
||||||
shutil.copy(sig_path, self.game_folder)
|
|
||||||
|
|
||||||
self.finished.emit(True, "", self.game_version)
|
|
||||||
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
|
||||||
self.finished.emit(False, f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n", self.game_version)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFetchThread(QThread):
|
|
||||||
finished = Signal(object, str) # data, error_message
|
|
||||||
|
|
||||||
def __init__(self, url, headers, debug_mode=False, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.url = url
|
|
||||||
self.headers = headers
|
|
||||||
self.debug_mode = debug_mode
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
if self.debug_mode:
|
|
||||||
print("--- Starting to fetch cloud config ---")
|
|
||||||
print(f"DEBUG: Requesting URL: {self.url}")
|
|
||||||
print(f"DEBUG: Using Headers: {self.headers}")
|
|
||||||
|
|
||||||
response = requests.get(self.url, headers=self.headers, timeout=10)
|
|
||||||
|
|
||||||
if self.debug_mode:
|
|
||||||
print(f"DEBUG: Response Status Code: {response.status_code}")
|
|
||||||
print(f"DEBUG: Response Headers: {response.headers}")
|
|
||||||
print(f"DEBUG: Response Text: {response.text}")
|
|
||||||
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
# 首先,总是尝试解析JSON
|
|
||||||
config_data = response.json()
|
|
||||||
|
|
||||||
# 检查是否是要求更新的错误信息
|
|
||||||
if config_data.get("message") == "请使用最新版本的FRAISEMOE Addons Installer NEXT进行下载安装":
|
|
||||||
self.finished.emit(None, "update_required")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 检查是否是有效的配置文件
|
|
||||||
required_keys = [f"vol.{i+1}.data" for i in range(4)] + ["after.data"]
|
|
||||||
missing_keys = [key for key in required_keys if key not in config_data]
|
|
||||||
if missing_keys:
|
|
||||||
self.finished.emit(None, f"missing_keys:{','.join(missing_keys)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.finished.emit(config_data, "")
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
self.finished.emit(None, f"网络请求失败: {e}")
|
|
||||||
except (ValueError, json.JSONDecodeError) as e:
|
|
||||||
self.finished.emit(None, f"JSON解析失败: {e}")
|
|
||||||
finally:
|
|
||||||
if self.debug_mode:
|
|
||||||
print("--- Finished fetching cloud config ---")
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -170,45 +30,42 @@ class MainWindow(QMainWindow):
|
|||||||
# 初始化UI (从Ui_install.py导入)
|
# 初始化UI (从Ui_install.py导入)
|
||||||
self.ui = Ui_MainWindows()
|
self.ui = Ui_MainWindows()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
icon_data = img_data.get("icon")
|
|
||||||
if icon_data:
|
|
||||||
pixmap = load_base64_image(icon_data)
|
|
||||||
self.setWindowIcon(QIcon(pixmap))
|
|
||||||
|
|
||||||
# 设置窗口标题为APP_NAME加版本号
|
|
||||||
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
|
||||||
|
|
||||||
# 初始化动画系统 (从animations.py导入)
|
# 初始化配置
|
||||||
self.animator = MultiStageAnimations(self.ui, self)
|
|
||||||
|
|
||||||
# 初始化功能变量
|
|
||||||
self.selected_folder = ""
|
|
||||||
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
|
|
||||||
self.installed_status["NEKOPARA After"] = False # 添加After的状态
|
|
||||||
self.download_queue = deque()
|
|
||||||
self.current_download_thread = None
|
|
||||||
self.hash_manager = HashManager(BLOCK_SIZE)
|
|
||||||
self.hash_thread = None
|
|
||||||
self.extraction_thread = None
|
|
||||||
self.hash_msg_box = None
|
|
||||||
self.optimized_ip = None
|
|
||||||
self.optimization_done = False # 标记是否已执行过优选
|
|
||||||
self.logger = None
|
|
||||||
self.hosts_manager = HostsManager() # 实例化HostsManager
|
|
||||||
self.cloud_config = None
|
|
||||||
self.config_fetch_thread = None
|
|
||||||
|
|
||||||
# 加载配置
|
|
||||||
self.config = load_config()
|
self.config = load_config()
|
||||||
|
|
||||||
|
# 初始化状态变量
|
||||||
|
self.cloud_config = None
|
||||||
|
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
|
||||||
|
self.installed_status["NEKOPARA After"] = False # 添加After的状态
|
||||||
|
self.hash_msg_box = None
|
||||||
|
self.progress_window = None
|
||||||
|
|
||||||
|
# 初始化工具类
|
||||||
|
self.hash_manager = HashManager(BLOCK_SIZE)
|
||||||
|
self.admin_privileges = AdminPrivileges()
|
||||||
|
|
||||||
|
# 初始化管理器
|
||||||
|
self.animator = MultiStageAnimations(self.ui, self)
|
||||||
|
self.ui_manager = UIManager(self)
|
||||||
|
|
||||||
|
# 首先设置UI - 确保debug_action已初始化
|
||||||
|
self.ui_manager.setup_ui()
|
||||||
|
|
||||||
|
self.debug_manager = DebugManager(self)
|
||||||
|
self.download_manager = DownloadManager(self)
|
||||||
|
|
||||||
|
# 设置退出按钮和开始安装按钮的样式表,使其在禁用状态下不会变灰
|
||||||
|
button_style = "QPushButton:disabled { opacity: 1.0; }"
|
||||||
|
self.ui.exit_btn.setStyleSheet(button_style)
|
||||||
|
self.ui.start_install_btn.setStyleSheet(button_style)
|
||||||
|
|
||||||
# 检查管理员权限和进程
|
# 检查管理员权限和进程
|
||||||
admin_privileges = AdminPrivileges()
|
self.admin_privileges.request_admin_privileges()
|
||||||
admin_privileges.request_admin_privileges()
|
self.admin_privileges.check_and_terminate_processes()
|
||||||
admin_privileges.check_and_terminate_processes()
|
|
||||||
|
|
||||||
# 备份hosts文件
|
# 备份hosts文件
|
||||||
self.hosts_manager.backup()
|
self.download_manager.hosts_manager.backup()
|
||||||
|
|
||||||
# 创建缓存目录
|
# 创建缓存目录
|
||||||
if not os.path.exists(PLUGIN):
|
if not os.path.exists(PLUGIN):
|
||||||
@@ -222,32 +79,14 @@ class MainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 连接信号 (使用Ui_install.py中的组件名称)
|
# 连接信号
|
||||||
self.ui.start_install_btn.clicked.connect(self.file_dialog)
|
self.ui.start_install_btn.clicked.connect(self.download_manager.file_dialog)
|
||||||
self.ui.exit_btn.clicked.connect(self.shutdown_app)
|
self.ui.exit_btn.clicked.connect(self.shutdown_app)
|
||||||
|
|
||||||
# “帮助”菜单
|
|
||||||
project_home_action = QAction("项目主页", self)
|
|
||||||
project_home_action.triggered.connect(self.open_project_home_page)
|
|
||||||
about_action = QAction("关于", self)
|
|
||||||
about_action.triggered.connect(self.show_about_dialog)
|
|
||||||
self.ui.menu_2.addAction(project_home_action)
|
|
||||||
self.ui.menu_2.addAction(about_action)
|
|
||||||
|
|
||||||
# “设置”菜单
|
|
||||||
self.debug_action = QAction("Debug模式", self, checkable=True)
|
|
||||||
self.debug_action.setChecked(self.config.get("debug_mode", False))
|
|
||||||
self.debug_action.triggered.connect(self.toggle_debug_mode)
|
|
||||||
self.ui.menu.addAction(self.debug_action)
|
|
||||||
|
|
||||||
# 为未来功能预留的“切换下载源”按钮
|
|
||||||
self.switch_source_action = QAction("切换下载源", self)
|
|
||||||
self.switch_source_action.setEnabled(False) # 暂时禁用
|
|
||||||
self.ui.menu.addAction(self.switch_source_action)
|
|
||||||
|
|
||||||
# 根据初始配置决定是否开启Debug模式
|
# 根据初始配置决定是否开启Debug模式
|
||||||
if self.debug_action.isChecked():
|
if hasattr(self.ui_manager, 'debug_action') and self.ui_manager.debug_action:
|
||||||
self.start_logging()
|
if self.ui_manager.debug_action.isChecked():
|
||||||
|
self.debug_manager.start_logging()
|
||||||
|
|
||||||
# 在窗口显示前设置初始状态
|
# 在窗口显示前设置初始状态
|
||||||
self.animator.initialize()
|
self.animator.initialize()
|
||||||
@@ -256,437 +95,159 @@ class MainWindow(QMainWindow):
|
|||||||
QTimer.singleShot(100, self.start_animations)
|
QTimer.singleShot(100, self.start_animations)
|
||||||
|
|
||||||
def start_animations(self):
|
def start_animations(self):
|
||||||
self.ui.exit_btn.setEnabled(False)
|
"""开始启动动画"""
|
||||||
|
# 不再禁用退出按钮的交互性,只通过样式表控制外观
|
||||||
|
# 但仍然需要跟踪动画状态,防止用户在动画播放过程中退出
|
||||||
|
self.animation_in_progress = True
|
||||||
|
|
||||||
|
# 禁用开始安装按钮,防止在动画播放期间点击
|
||||||
|
self.ui.start_install_btn.setEnabled(False)
|
||||||
|
|
||||||
self.animator.animation_finished.connect(self.on_animations_finished)
|
self.animator.animation_finished.connect(self.on_animations_finished)
|
||||||
self.animator.start_animations()
|
self.animator.start_animations()
|
||||||
self.fetch_cloud_config()
|
self.fetch_cloud_config()
|
||||||
|
|
||||||
def on_animations_finished(self):
|
def on_animations_finished(self):
|
||||||
self.ui.exit_btn.setEnabled(True)
|
"""动画完成后启用按钮"""
|
||||||
|
self.animation_in_progress = False
|
||||||
|
|
||||||
|
# 启用开始安装按钮
|
||||||
|
self.ui.start_install_btn.setEnabled(True)
|
||||||
|
|
||||||
def fetch_cloud_config(self):
|
def fetch_cloud_config(self):
|
||||||
|
"""获取云端配置"""
|
||||||
headers = {"User-Agent": UA}
|
headers = {"User-Agent": UA}
|
||||||
debug_mode = self.debug_action.isChecked()
|
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
|
||||||
self.config_fetch_thread = ConfigFetchThread(CONFIG_URL, headers, debug_mode, self)
|
self.config_fetch_thread = ConfigFetchThread(CONFIG_URL, headers, debug_mode, self)
|
||||||
self.config_fetch_thread.finished.connect(self.on_config_fetched)
|
self.config_fetch_thread.finished.connect(self.on_config_fetched)
|
||||||
self.config_fetch_thread.start()
|
self.config_fetch_thread.start()
|
||||||
|
|
||||||
def on_config_fetched(self, data, error_message):
|
def on_config_fetched(self, data, error_message):
|
||||||
|
"""云端配置获取完成的回调处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: 获取到的配置数据
|
||||||
|
error_message: 错误信息,如果有
|
||||||
|
"""
|
||||||
if error_message:
|
if error_message:
|
||||||
if error_message == "update_required":
|
if error_message == "update_required":
|
||||||
msg_box = msgbox_frame(
|
msg_box = msgbox_frame(
|
||||||
f"更新提示 - {APP_NAME}",
|
f"更新提示 - {APP_NAME}",
|
||||||
"\n当前版本过低,请及时更新。\n",
|
"\n当前版本过低,请及时更新。\n",
|
||||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
QMessageBox.StandardButton.Ok,
|
||||||
)
|
)
|
||||||
msg_box.exec()
|
msg_box.exec()
|
||||||
self.open_project_home_page()
|
self.ui_manager.open_project_home_page()
|
||||||
self.shutdown_app(force_exit=True)
|
self.shutdown_app(force_exit=True)
|
||||||
elif "missing_keys" in error_message:
|
elif "missing_keys" in error_message:
|
||||||
missing_versions = error_message.split(":")[1]
|
missing_versions = error_message.split(":")[1]
|
||||||
msg_box = msgbox_frame(
|
msg_box = msgbox_frame(
|
||||||
f"配置缺失 - {APP_NAME}",
|
f"配置缺失 - {APP_NAME}",
|
||||||
f'\n云端缺失下载链接,可能云服务器正在维护,不影响其他版本下载。\n当前缺失版本:"{missing_versions}"\n',
|
f'\n云端缺失下载链接,可能云服务器正在维护,不影响其他版本下载。\n当前缺失版本:"{missing_versions}"\n',
|
||||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
QMessageBox.StandardButton.Ok,
|
||||||
)
|
)
|
||||||
msg_box.exec()
|
msg_box.exec()
|
||||||
else:
|
else:
|
||||||
# 其他错误暂时只在debug模式下打印
|
# 其他错误暂时只在debug模式下打印
|
||||||
if self.debug_action.isChecked():
|
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
|
||||||
|
if debug_mode:
|
||||||
print(f"获取云端配置失败: {error_message}")
|
print(f"获取云端配置失败: {error_message}")
|
||||||
else:
|
else:
|
||||||
self.cloud_config = data
|
self.cloud_config = data
|
||||||
if self.debug_action.isChecked():
|
debug_mode = self.ui_manager.debug_action.isChecked() if self.ui_manager.debug_action else False
|
||||||
|
if debug_mode:
|
||||||
print("--- Cloud config fetched successfully ---")
|
print("--- Cloud config fetched successfully ---")
|
||||||
print(json.dumps(data, indent=2))
|
print(json.dumps(data, indent=2))
|
||||||
|
|
||||||
def toggle_debug_mode(self, checked):
|
def toggle_debug_mode(self, checked):
|
||||||
self.config["debug_mode"] = checked
|
"""切换调试模式
|
||||||
save_config(self.config)
|
|
||||||
if checked:
|
|
||||||
self.start_logging()
|
|
||||||
else:
|
|
||||||
self.stop_logging()
|
|
||||||
|
|
||||||
def start_logging(self):
|
|
||||||
if self.logger is None:
|
|
||||||
try:
|
|
||||||
if os.path.exists(LOG_FILE):
|
|
||||||
os.remove(LOG_FILE)
|
|
||||||
# 保存原始的 stdout 和 stderr
|
|
||||||
self.original_stdout = sys.stdout
|
|
||||||
self.original_stderr = sys.stderr
|
|
||||||
# 创建 Logger 实例
|
|
||||||
self.logger = Logger(LOG_FILE, self.original_stdout)
|
|
||||||
sys.stdout = self.logger
|
|
||||||
sys.stderr = self.logger
|
|
||||||
print("--- Debug mode enabled ---")
|
|
||||||
except (IOError, OSError) as e:
|
|
||||||
QtWidgets.QMessageBox.critical(self, "错误", f"无法创建日志文件: {e}")
|
|
||||||
self.logger = None
|
|
||||||
|
|
||||||
def stop_logging(self):
|
|
||||||
if self.logger:
|
|
||||||
print("--- Debug mode disabled ---")
|
|
||||||
sys.stdout = self.original_stdout
|
|
||||||
sys.stderr = self.original_stderr
|
|
||||||
self.logger.close()
|
|
||||||
self.logger = None
|
|
||||||
|
|
||||||
def get_install_paths(self):
|
Args:
|
||||||
return {
|
checked: 是否启用调试模式
|
||||||
game: os.path.join(self.selected_folder, info["install_path"])
|
"""
|
||||||
for game, info in GAME_INFO.items()
|
self.debug_manager.toggle_debug_mode(checked)
|
||||||
}
|
|
||||||
|
def save_config(self, config):
|
||||||
def file_dialog(self):
|
"""保存配置的便捷方法"""
|
||||||
self.selected_folder = QFileDialog.getExistingDirectory(
|
save_config(config)
|
||||||
self, f"选择游戏所在【上级目录】 {APP_NAME}"
|
|
||||||
)
|
def create_download_thread(self, url, _7z_path, game_version):
|
||||||
if not self.selected_folder:
|
"""创建下载线程
|
||||||
QtWidgets.QMessageBox.warning(
|
|
||||||
self, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
Args:
|
||||||
)
|
url: 下载URL
|
||||||
return
|
_7z_path: 7z文件保存路径
|
||||||
self.download_action()
|
game_version: 游戏版本
|
||||||
|
|
||||||
def get_download_url(self) -> dict:
|
|
||||||
try:
|
|
||||||
if self.cloud_config:
|
|
||||||
if self.debug_action.isChecked():
|
|
||||||
print("--- Using pre-fetched cloud config ---")
|
|
||||||
config_data = self.cloud_config
|
|
||||||
else:
|
|
||||||
# 如果没有预加载的配置,则同步获取
|
|
||||||
headers = {"User-Agent": UA}
|
|
||||||
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
|
||||||
response.raise_for_status()
|
|
||||||
config_data = response.json()
|
|
||||||
|
|
||||||
if not config_data:
|
|
||||||
raise ValueError("未能获取或解析配置数据")
|
|
||||||
|
|
||||||
if self.debug_action.isChecked():
|
|
||||||
print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}")
|
|
||||||
|
|
||||||
# 统一处理URL提取,确保返回扁平化的字典
|
|
||||||
urls = {}
|
|
||||||
for i in range(4):
|
|
||||||
key = f"vol.{i+1}.data"
|
|
||||||
if key in config_data and "url" in config_data[key]:
|
|
||||||
urls[f"vol{i+1}"] = config_data[key]["url"]
|
|
||||||
|
|
||||||
if "after.data" in config_data and "url" in config_data["after.data"]:
|
Returns:
|
||||||
urls["after"] = config_data["after.data"]["url"]
|
DownloadThread: 下载线程实例
|
||||||
|
"""
|
||||||
# 检查是否成功提取了所有URL
|
return DownloadThread(url, _7z_path, game_version, self)
|
||||||
if len(urls) != 5:
|
|
||||||
missing_keys_map = {
|
def create_progress_window(self):
|
||||||
f"vol{i+1}": f"vol.{i+1}.data" for i in range(4)
|
"""创建下载进度窗口
|
||||||
}
|
|
||||||
missing_keys_map["after"] = "after.data"
|
Returns:
|
||||||
|
ProgressWindow: 进度窗口实例
|
||||||
extracted_keys = set(urls.keys())
|
"""
|
||||||
all_keys = set(missing_keys_map.keys())
|
return ProgressWindow(self)
|
||||||
missing_simple_keys = all_keys - extracted_keys
|
|
||||||
|
def create_hash_thread(self, mode, install_paths):
|
||||||
missing_original_keys = [missing_keys_map[k] for k in missing_simple_keys]
|
"""创建哈希检查线程
|
||||||
raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_original_keys)}")
|
|
||||||
|
Args:
|
||||||
if self.debug_action.isChecked():
|
mode: 检查模式,"pre"或"after"
|
||||||
print(f"DEBUG: Extracted URLs: {urls}")
|
install_paths: 安装路径字典
|
||||||
print("--- Finished getting download URL successfully ---")
|
|
||||||
return urls
|
|
||||||
if self.debug_action.isChecked():
|
|
||||||
print(f"DEBUG: Extracted URLs: {urls}")
|
|
||||||
print("--- Finished getting download URL successfully ---")
|
|
||||||
return urls
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
status_code = e.response.status_code if e.response is not None else "未知"
|
|
||||||
try:
|
|
||||||
error_response = e.response.json() if e.response else {}
|
|
||||||
json_title = error_response.get("title", "无错误类型")
|
|
||||||
json_message = error_response.get("message", "无附加错误信息")
|
|
||||||
except (ValueError, AttributeError):
|
|
||||||
json_title = "配置文件异常,无法解析错误类型"
|
|
||||||
json_message = "配置文件异常,无法解析错误信息"
|
|
||||||
|
|
||||||
if self.debug_action.isChecked():
|
|
||||||
print(f"ERROR: Failed to get download config due to RequestException: {e}")
|
|
||||||
|
|
||||||
QtWidgets.QMessageBox.critical(
|
Returns:
|
||||||
self,
|
HashThread: 哈希检查线程实例
|
||||||
f"错误 - {APP_NAME}",
|
"""
|
||||||
f"\n下载配置获取失败\n\n【HTTP状态】:{status_code}\n【错误类型】:{json_title}\n【错误信息】:{json_message}\n",
|
return HashThread(mode, install_paths, PLUGIN_HASH, self.installed_status, self)
|
||||||
)
|
|
||||||
return {}
|
|
||||||
except ValueError as e:
|
|
||||||
if self.debug_action.isChecked():
|
|
||||||
print(f"ERROR: Failed to parse download config due to ValueError: {e}")
|
|
||||||
|
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
f"错误 - {APP_NAME}",
|
|
||||||
f"\n配置文件格式异常\n\n【错误信息】:{e}\n",
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
|
||||||
game_exe = {
|
|
||||||
game: os.path.join(
|
|
||||||
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
|
|
||||||
)
|
|
||||||
for game, info in GAME_INFO.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
def create_extraction_thread(self, _7z_path, game_folder, plugin_path, game_version):
|
||||||
game_version not in game_exe
|
"""创建解压线程
|
||||||
or not os.path.exists(game_exe[game_version])
|
|
||||||
or self.installed_status[game_version]
|
Args:
|
||||||
):
|
_7z_path: 7z文件路径
|
||||||
self.installed_status[game_version] = False
|
game_folder: 游戏文件夹路径
|
||||||
self.show_result()
|
plugin_path: 插件路径
|
||||||
return
|
game_version: 游戏版本
|
||||||
|
|
||||||
self.progress_window = ProgressWindow(self)
|
Returns:
|
||||||
self.start_download_with_ip(self.optimized_ip, url, _7z_path, game_version, game_folder, plugin_path)
|
ExtractionThread: 解压线程实例
|
||||||
|
"""
|
||||||
|
return ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self)
|
||||||
def on_optimization_and_hosts_finished(self, ip):
|
|
||||||
self.optimized_ip = ip
|
|
||||||
self.optimization_done = True
|
|
||||||
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
|
||||||
if self.optimizing_msg_box.isVisible():
|
|
||||||
self.optimizing_msg_box.accept()
|
|
||||||
self.optimizing_msg_box = None
|
|
||||||
|
|
||||||
if not ip:
|
|
||||||
QtWidgets.QMessageBox.warning(self, f"优选失败 - {APP_NAME}", "\n未能找到合适的Cloudflare IP,将使用默认网络进行下载。\n")
|
|
||||||
else:
|
|
||||||
if self.download_queue:
|
|
||||||
first_url = self.download_queue[0][0]
|
|
||||||
hostname = urlparse(first_url).hostname
|
|
||||||
if self.hosts_manager.apply_ip(hostname, ip):
|
|
||||||
QtWidgets.QMessageBox.information(self, f"成功 - {APP_NAME}", f"\n已将优选IP ({ip}) 应用到hosts文件。\n")
|
|
||||||
else:
|
|
||||||
QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", "\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n")
|
|
||||||
|
|
||||||
self.next_download_task()
|
def after_hash_compare(self):
|
||||||
|
"""进行安装后哈希比较"""
|
||||||
def start_download_with_ip(self, preferred_ip, url, _7z_path, game_version, game_folder, plugin_path):
|
# 禁用退出按钮
|
||||||
if preferred_ip:
|
self.ui.exit_btn.setEnabled(False)
|
||||||
print(f"已为 {game_version} 获取到优选IP: {preferred_ip}")
|
|
||||||
else:
|
|
||||||
print(f"未能为 {game_version} 获取优选IP,将使用默认线路。")
|
|
||||||
|
|
||||||
self.current_download_thread = DownloadThread(url, _7z_path, game_version, self)
|
|
||||||
self.current_download_thread.progress.connect(self.progress_window.update_progress)
|
|
||||||
self.current_download_thread.finished.connect(
|
|
||||||
lambda success, error: self.install_setting(
|
|
||||||
success,
|
|
||||||
error,
|
|
||||||
self.progress_window,
|
|
||||||
url,
|
|
||||||
game_folder,
|
|
||||||
game_version,
|
|
||||||
_7z_path,
|
|
||||||
plugin_path,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.progress_window.stop_button.clicked.connect(self.current_download_thread.stop)
|
|
||||||
self.current_download_thread.start()
|
|
||||||
self.progress_window.exec()
|
|
||||||
|
|
||||||
def install_setting(
|
|
||||||
self,
|
|
||||||
success,
|
|
||||||
error,
|
|
||||||
progress_window,
|
|
||||||
url,
|
|
||||||
game_folder,
|
|
||||||
game_version,
|
|
||||||
_7z_path,
|
|
||||||
plugin_path,
|
|
||||||
):
|
|
||||||
if progress_window.isVisible():
|
|
||||||
progress_window.reject()
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
print(f"--- Download Failed: {game_version} ---")
|
|
||||||
print(error)
|
|
||||||
print("------------------------------------")
|
|
||||||
msg_box = QtWidgets.QMessageBox(self)
|
|
||||||
msg_box.setWindowTitle(f"下载失败 - {APP_NAME}")
|
|
||||||
msg_box.setText(f"\n文件获取失败: {game_version}\n错误: {error}\n\n是否重试?")
|
|
||||||
|
|
||||||
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
|
||||||
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
|
||||||
end_button = msg_box.addButton("结束", QtWidgets.QMessageBox.ButtonRole.RejectRole)
|
|
||||||
|
|
||||||
msg_box.exec()
|
|
||||||
clicked_button = msg_box.clickedButton()
|
|
||||||
|
|
||||||
if clicked_button == retry_button:
|
|
||||||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
|
||||||
elif clicked_button == next_button:
|
|
||||||
self.next_download_task()
|
|
||||||
else:
|
|
||||||
self.on_download_stopped()
|
|
||||||
return
|
|
||||||
|
|
||||||
# --- Start Extraction in a new thread ---
|
|
||||||
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
|
||||||
|
|
||||||
self.extraction_thread = ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self)
|
|
||||||
self.extraction_thread.finished.connect(self.on_extraction_finished)
|
|
||||||
self.extraction_thread.start()
|
|
||||||
|
|
||||||
def on_extraction_finished(self, success, error_message, game_version):
|
|
||||||
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
|
||||||
self.hash_msg_box.close()
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", error_message)
|
|
||||||
self.installed_status[game_version] = False
|
|
||||||
else:
|
|
||||||
self.installed_status[game_version] = True
|
|
||||||
|
|
||||||
self.next_download_task()
|
|
||||||
|
|
||||||
def download_action(self):
|
|
||||||
# 询问用户是否使用Cloudflare加速
|
|
||||||
msg_box = QMessageBox(self)
|
|
||||||
msg_box.setWindowTitle(f"下载优化 - {APP_NAME}")
|
|
||||||
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。")
|
|
||||||
msg_box.setIcon(QMessageBox.Icon.Question)
|
|
||||||
|
|
||||||
yes_button = msg_box.addButton("是,开启加速", QMessageBox.ButtonRole.YesRole)
|
|
||||||
no_button = msg_box.addButton("否,直接下载", QMessageBox.ButtonRole.NoRole)
|
|
||||||
|
|
||||||
msg_box.exec()
|
|
||||||
|
|
||||||
use_optimization = msg_box.clickedButton() == yes_button
|
|
||||||
|
|
||||||
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||||
|
|
||||||
install_paths = self.get_install_paths()
|
install_paths = self.download_manager.get_install_paths()
|
||||||
|
|
||||||
self.hash_thread = HashThread("pre", install_paths, PLUGIN_HASH, self.installed_status, self)
|
self.hash_thread = self.create_hash_thread("after", install_paths)
|
||||||
# 将用户选择传递给哈希完成后的处理函数
|
|
||||||
self.hash_thread.pre_finished.connect(lambda status: self.on_pre_hash_finished(status, use_optimization))
|
|
||||||
self.hash_thread.start()
|
|
||||||
|
|
||||||
def on_pre_hash_finished(self, updated_status, use_optimization):
|
|
||||||
self.installed_status = updated_status
|
|
||||||
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
|
||||||
self.hash_msg_box.accept()
|
|
||||||
self.hash_msg_box = None
|
|
||||||
|
|
||||||
config = self.get_download_url()
|
|
||||||
if not config:
|
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
self, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# --- 填充下载队列 ---
|
|
||||||
for i in range(1, 5):
|
|
||||||
game_version = f"NEKOPARA Vol.{i}"
|
|
||||||
if not self.installed_status.get(game_version, False):
|
|
||||||
url = config.get(f"vol{i}")
|
|
||||||
if not url: continue
|
|
||||||
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
|
|
||||||
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
|
||||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
|
||||||
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
|
||||||
|
|
||||||
game_version = "NEKOPARA After"
|
|
||||||
if not self.installed_status.get(game_version, False):
|
|
||||||
url = config.get("after")
|
|
||||||
if url:
|
|
||||||
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
|
|
||||||
_7z_path = os.path.join(PLUGIN, "after.7z")
|
|
||||||
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
|
||||||
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
|
||||||
|
|
||||||
if not self.download_queue:
|
|
||||||
self.after_hash_compare(PLUGIN_HASH)
|
|
||||||
return
|
|
||||||
|
|
||||||
if use_optimization and not self.optimization_done:
|
|
||||||
first_url = self.download_queue[0][0]
|
|
||||||
self.optimizing_msg_box = msgbox_frame(
|
|
||||||
f"通知 - {APP_NAME}",
|
|
||||||
"\n正在优选Cloudflare IP,请稍候...\n\n这可能需要5-10分钟,请耐心等待喵~"
|
|
||||||
)
|
|
||||||
# 我们不再提供“跳过”按钮,因为用户已经做出了选择
|
|
||||||
self.optimizing_msg_box.setStandardButtons(QMessageBox.StandardButton.NoButton)
|
|
||||||
self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal)
|
|
||||||
self.optimizing_msg_box.open()
|
|
||||||
|
|
||||||
self.ip_optimizer_thread = IpOptimizerThread(first_url)
|
|
||||||
# 优选完成后,需要修改hosts并开始下载
|
|
||||||
self.ip_optimizer_thread.finished.connect(self.on_optimization_and_hosts_finished)
|
|
||||||
self.ip_optimizer_thread.start()
|
|
||||||
else:
|
|
||||||
# 如果用户选择不优化,或已经优化过,直接开始下载
|
|
||||||
self.next_download_task()
|
|
||||||
|
|
||||||
def next_download_task(self):
|
|
||||||
if not self.download_queue:
|
|
||||||
self.after_hash_compare(PLUGIN_HASH)
|
|
||||||
return
|
|
||||||
# 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务
|
|
||||||
if self.current_download_thread and self.current_download_thread.isRunning():
|
|
||||||
return
|
|
||||||
|
|
||||||
# 在开始下载前,确保hosts文件已修改(如果需要)
|
|
||||||
# 这里的逻辑保持不变,因为hosts文件应该在队列开始前就被修改了
|
|
||||||
url, game_folder, game_version, _7z_path, plugin_path = self.download_queue.popleft()
|
|
||||||
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
|
||||||
|
|
||||||
def on_download_stopped(self):
|
|
||||||
"""当用户点击停止按钮或选择结束时调用的槽函数"""
|
|
||||||
# 停止IP优选线程
|
|
||||||
if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning():
|
|
||||||
self.ip_optimizer_thread.stop()
|
|
||||||
self.ip_optimizer_thread.wait()
|
|
||||||
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
|
||||||
if self.optimizing_msg_box.isVisible():
|
|
||||||
self.optimizing_msg_box.accept()
|
|
||||||
self.optimizing_msg_box = None
|
|
||||||
|
|
||||||
# 停止当前可能仍在运行的下载线程
|
|
||||||
if self.current_download_thread and self.current_download_thread.isRunning():
|
|
||||||
self.current_download_thread.stop()
|
|
||||||
self.current_download_thread.wait() # 等待线程完全终止
|
|
||||||
|
|
||||||
# 清空下载队列,因为用户决定停止
|
|
||||||
self.download_queue.clear()
|
|
||||||
|
|
||||||
# 确保进度窗口已关闭
|
|
||||||
if hasattr(self, 'progress_window') and self.progress_window.isVisible():
|
|
||||||
self.progress_window.reject()
|
|
||||||
|
|
||||||
# 可以在这里决定是否立即进行哈希比较或显示结果
|
|
||||||
print("下载已全部停止。")
|
|
||||||
self.setEnabled(True) # 恢复主窗口交互
|
|
||||||
self.show_result()
|
|
||||||
|
|
||||||
def after_hash_compare(self, plugin_hash):
|
|
||||||
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
|
||||||
|
|
||||||
install_paths = self.get_install_paths()
|
|
||||||
|
|
||||||
self.hash_thread = HashThread("after", install_paths, plugin_hash, self.installed_status, self)
|
|
||||||
self.hash_thread.after_finished.connect(self.on_after_hash_finished)
|
self.hash_thread.after_finished.connect(self.on_after_hash_finished)
|
||||||
self.hash_thread.start()
|
self.hash_thread.start()
|
||||||
|
|
||||||
def on_after_hash_finished(self, result):
|
def on_after_hash_finished(self, result):
|
||||||
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
"""哈希比较完成后的处理
|
||||||
self.hash_msg_box.close()
|
|
||||||
|
Args:
|
||||||
|
result: 哈希比较结果
|
||||||
|
"""
|
||||||
|
# 确保哈希检查窗口关闭,无论是否还在显示
|
||||||
|
if self.hash_msg_box:
|
||||||
|
try:
|
||||||
|
if self.hash_msg_box.isVisible():
|
||||||
|
self.hash_msg_box.close()
|
||||||
|
else:
|
||||||
|
# 如果窗口已经不可见但没有关闭,也要尝试关闭
|
||||||
|
self.hash_msg_box.close()
|
||||||
|
except:
|
||||||
|
pass # 忽略任何关闭窗口时的错误
|
||||||
|
self.hash_msg_box = None
|
||||||
|
|
||||||
if not result["passed"]:
|
if not result["passed"]:
|
||||||
game = result.get("game", "未知游戏")
|
game = result.get("game", "未知游戏")
|
||||||
@@ -694,94 +255,95 @@ class MainWindow(QMainWindow):
|
|||||||
msg_box = msgbox_frame(
|
msg_box = msgbox_frame(
|
||||||
f"文件校验失败 - {APP_NAME}",
|
f"文件校验失败 - {APP_NAME}",
|
||||||
message,
|
message,
|
||||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
QMessageBox.StandardButton.Ok,
|
||||||
)
|
)
|
||||||
msg_box.exec()
|
msg_box.exec()
|
||||||
|
|
||||||
self.show_result()
|
# 重新启用退出按钮和开始安装按钮
|
||||||
|
self.ui.exit_btn.setEnabled(True)
|
||||||
|
self.ui.start_install_btn.setEnabled(True)
|
||||||
|
|
||||||
|
# 添加短暂延迟确保UI更新
|
||||||
|
QTimer.singleShot(100, self.show_result)
|
||||||
|
|
||||||
def show_result(self):
|
def show_result(self):
|
||||||
|
"""显示安装结果"""
|
||||||
installed_version = "\n".join(
|
installed_version = "\n".join(
|
||||||
[i for i in self.installed_status if self.installed_status[i]]
|
[i for i in self.installed_status if self.installed_status[i]]
|
||||||
)
|
)
|
||||||
failed_ver = "\n".join(
|
failed_ver = "\n".join(
|
||||||
[i for i in self.installed_status if not self.installed_status[i]]
|
[i for i in self.installed_status if not self.installed_status[i]]
|
||||||
)
|
)
|
||||||
QtWidgets.QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
f"完成 - {APP_NAME}",
|
f"完成 - {APP_NAME}",
|
||||||
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
|
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
|
||||||
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
|
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_about_dialog(self):
|
|
||||||
"""显示关于对话框"""
|
|
||||||
about_text = f"""
|
|
||||||
<p><b>{APP_NAME} v{APP_VERSION}</b></p>
|
|
||||||
<p>原作: <a href="https://github.com/Yanam1Anna">Yanam1Anna</a></p>
|
|
||||||
<p>此应用根据 <a href="https://github.com/hyb-oyqq/FRAISEMOE2-Installer/blob/master/LICENSE">GPL-3.0 许可证</a> 授权。</p>
|
|
||||||
"""
|
|
||||||
msg_box = msgbox_frame(
|
|
||||||
f"关于 - {APP_NAME}",
|
|
||||||
about_text,
|
|
||||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
|
||||||
)
|
|
||||||
msg_box.setTextFormat(Qt.TextFormat.RichText) # 启用富文本
|
|
||||||
msg_box.exec()
|
|
||||||
|
|
||||||
def open_project_home_page(self):
|
|
||||||
"""打开项目主页"""
|
|
||||||
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
|
"""窗口关闭事件处理
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event: 关闭事件
|
||||||
|
"""
|
||||||
self.shutdown_app(event)
|
self.shutdown_app(event)
|
||||||
|
|
||||||
def shutdown_app(self, event=None, force_exit=False):
|
def shutdown_app(self, event=None, force_exit=False):
|
||||||
self.hosts_manager.restore() # 恢复hosts文件
|
"""关闭应用程序
|
||||||
self.stop_logging() # 确保在退出时停止日志记录
|
|
||||||
|
Args:
|
||||||
|
event: 关闭事件,如果是从closeEvent调用的
|
||||||
|
force_exit: 是否强制退出
|
||||||
|
"""
|
||||||
|
# 检查是否有动画或任务正在进行
|
||||||
|
if hasattr(self, 'animation_in_progress') and self.animation_in_progress and not force_exit:
|
||||||
|
# 如果动画正在进行,阻止退出
|
||||||
|
if event:
|
||||||
|
event.ignore()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查是否有下载任务正在进行
|
||||||
|
if hasattr(self.download_manager, 'current_download_thread') and \
|
||||||
|
self.download_manager.current_download_thread and \
|
||||||
|
self.download_manager.current_download_thread.isRunning() and not force_exit:
|
||||||
|
# 询问用户是否确认退出
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
f"确认退出 - {APP_NAME}",
|
||||||
|
"\n下载任务正在进行中,确定要退出吗?\n",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
|
QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.StandardButton.No:
|
||||||
|
if event:
|
||||||
|
event.ignore()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 恢复hosts文件
|
||||||
|
self.download_manager.hosts_manager.restore()
|
||||||
|
|
||||||
|
# 额外检查并清理hosts文件中的残留记录
|
||||||
|
self.download_manager.hosts_manager.check_and_clean_all_entries()
|
||||||
|
|
||||||
|
# 停止日志记录
|
||||||
|
self.debug_manager.stop_logging()
|
||||||
|
|
||||||
if not force_exit:
|
if not force_exit:
|
||||||
reply = QtWidgets.QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"退出程序",
|
f"确认退出 - {APP_NAME}",
|
||||||
"\n是否确定退出?\n",
|
"\n确定要退出吗?\n",
|
||||||
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QtWidgets.QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.No
|
||||||
)
|
)
|
||||||
if reply != QtWidgets.QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.No:
|
||||||
if event:
|
if event:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
# 退出应用程序
|
||||||
self.current_download_thread
|
|
||||||
and self.current_download_thread.isRunning()
|
|
||||||
):
|
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
f"错误 - {APP_NAME}",
|
|
||||||
"\n当前有下载任务正在进行,完成后再试\n",
|
|
||||||
)
|
|
||||||
if event:
|
|
||||||
event.ignore()
|
|
||||||
return
|
|
||||||
|
|
||||||
if os.path.exists(PLUGIN):
|
|
||||||
for attempt in range(3):
|
|
||||||
try:
|
|
||||||
shutil.rmtree(PLUGIN)
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
if attempt == 2:
|
|
||||||
QtWidgets.QMessageBox.critical(
|
|
||||||
self,
|
|
||||||
f"错误 - {APP_NAME}",
|
|
||||||
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
|
|
||||||
)
|
|
||||||
if event:
|
|
||||||
event.accept()
|
|
||||||
sys.exit(1)
|
|
||||||
if event:
|
if event:
|
||||||
event.accept()
|
event.accept()
|
||||||
else:
|
else:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
656
source/main_window.py.bak
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import webbrowser
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from PySide6 import QtWidgets
|
||||||
|
from PySide6.QtCore import QTimer, Qt
|
||||||
|
from PySide6.QtGui import QIcon, QAction
|
||||||
|
from PySide6.QtWidgets import QMainWindow, QFileDialog, QApplication, QMessageBox
|
||||||
|
|
||||||
|
from ui.Ui_install import Ui_MainWindows
|
||||||
|
from core.animations import MultiStageAnimations
|
||||||
|
from data.config import (
|
||||||
|
APP_NAME, APP_VERSION, PLUGIN, GAME_INFO, BLOCK_SIZE,
|
||||||
|
PLUGIN_HASH, UA, CONFIG_URL, LOG_FILE
|
||||||
|
)
|
||||||
|
from utils import (
|
||||||
|
load_base64_image, HashManager, AdminPrivileges, msgbox_frame,
|
||||||
|
load_config, save_config, HostsManager, Logger
|
||||||
|
)
|
||||||
|
from workers.download import DownloadThread, ProgressWindow
|
||||||
|
from data.pic_data import img_data
|
||||||
|
from workers import (
|
||||||
|
IpOptimizerThread, HashThread, ExtractionThread, ConfigFetchThread
|
||||||
|
)
|
||||||
|
from core import (
|
||||||
|
MultiStageAnimations, UIManager, DownloadManager, DebugManager
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# 初始化UI (从Ui_install.py导入)
|
||||||
|
self.ui = Ui_MainWindows()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
|
icon_data = img_data.get("icon")
|
||||||
|
if icon_data:
|
||||||
|
pixmap = load_base64_image(icon_data)
|
||||||
|
self.setWindowIcon(QIcon(pixmap))
|
||||||
|
|
||||||
|
# 设置窗口标题为APP_NAME加版本号
|
||||||
|
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
|
||||||
|
|
||||||
|
# 初始化动画系统 (从animations.py导入)
|
||||||
|
self.animator = MultiStageAnimations(self.ui, self)
|
||||||
|
|
||||||
|
# 初始化功能变量
|
||||||
|
self.selected_folder = ""
|
||||||
|
self.installed_status = {f"NEKOPARA Vol.{i}": False for i in range(1, 5)}
|
||||||
|
self.installed_status["NEKOPARA After"] = False # 添加After的状态
|
||||||
|
self.download_queue = deque()
|
||||||
|
self.current_download_thread = None
|
||||||
|
self.hash_manager = HashManager(BLOCK_SIZE)
|
||||||
|
self.hash_thread = None
|
||||||
|
self.extraction_thread = None
|
||||||
|
self.hash_msg_box = None
|
||||||
|
self.optimized_ip = None
|
||||||
|
self.optimization_done = False # 标记是否已执行过优选
|
||||||
|
self.logger = None
|
||||||
|
self.hosts_manager = HostsManager() # 实例化HostsManager
|
||||||
|
self.cloud_config = None
|
||||||
|
self.config_fetch_thread = None
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
self.config = load_config()
|
||||||
|
|
||||||
|
# 检查管理员权限和进程
|
||||||
|
self.admin_privileges = AdminPrivileges()
|
||||||
|
self.admin_privileges.request_admin_privileges()
|
||||||
|
self.admin_privileges.check_and_terminate_processes()
|
||||||
|
|
||||||
|
# 备份hosts文件
|
||||||
|
self.hosts_manager.backup()
|
||||||
|
|
||||||
|
# 创建缓存目录
|
||||||
|
if not os.path.exists(PLUGIN):
|
||||||
|
try:
|
||||||
|
os.makedirs(PLUGIN)
|
||||||
|
except OSError as e:
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
f"\n无法创建缓存位置\n\n使用管理员身份运行或检查文件读写权限\n\n【错误信息】:{e}\n",
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 连接信号 (使用Ui_install.py中的组件名称)
|
||||||
|
self.ui.start_install_btn.clicked.connect(self.download_manager.file_dialog)
|
||||||
|
self.ui.exit_btn.clicked.connect(self.shutdown_app)
|
||||||
|
|
||||||
|
# “帮助”菜单
|
||||||
|
project_home_action = QAction("项目主页", self)
|
||||||
|
project_home_action.triggered.connect(self.open_project_home_page)
|
||||||
|
about_action = QAction("关于", self)
|
||||||
|
about_action.triggered.connect(self.show_about_dialog)
|
||||||
|
self.ui.menu_2.addAction(project_home_action)
|
||||||
|
self.ui.menu_2.addAction(about_action)
|
||||||
|
|
||||||
|
# “设置”菜单
|
||||||
|
self.debug_action = QAction("Debug模式", self, checkable=True)
|
||||||
|
self.debug_action.setChecked(self.config.get("debug_mode", False))
|
||||||
|
self.debug_action.triggered.connect(self.toggle_debug_mode)
|
||||||
|
self.ui.menu.addAction(self.debug_action)
|
||||||
|
|
||||||
|
# 为未来功能预留的“切换下载源”按钮
|
||||||
|
self.switch_source_action = QAction("切换下载源", self)
|
||||||
|
self.switch_source_action.setEnabled(False) # 暂时禁用
|
||||||
|
self.ui.menu.addAction(self.switch_source_action)
|
||||||
|
|
||||||
|
# 根据初始配置决定是否开启Debug模式
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
self.start_logging()
|
||||||
|
|
||||||
|
# 在窗口显示前设置初始状态
|
||||||
|
self.animator.initialize()
|
||||||
|
|
||||||
|
# 窗口显示后延迟100ms启动动画
|
||||||
|
QTimer.singleShot(100, self.start_animations)
|
||||||
|
|
||||||
|
def start_animations(self):
|
||||||
|
self.ui.exit_btn.setEnabled(False)
|
||||||
|
self.animator.animation_finished.connect(self.on_animations_finished)
|
||||||
|
self.animator.start_animations()
|
||||||
|
self.fetch_cloud_config()
|
||||||
|
|
||||||
|
def on_animations_finished(self):
|
||||||
|
self.ui.exit_btn.setEnabled(True)
|
||||||
|
|
||||||
|
def fetch_cloud_config(self):
|
||||||
|
headers = {"User-Agent": UA}
|
||||||
|
debug_mode = self.debug_action.isChecked()
|
||||||
|
self.config_fetch_thread = ConfigFetchThread(CONFIG_URL, headers, debug_mode, self)
|
||||||
|
self.config_fetch_thread.finished.connect(self.on_config_fetched)
|
||||||
|
self.config_fetch_thread.start()
|
||||||
|
|
||||||
|
def on_config_fetched(self, data, error_message):
|
||||||
|
if error_message:
|
||||||
|
if error_message == "update_required":
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"更新提示 - {APP_NAME}",
|
||||||
|
"\n当前版本过低,请及时更新。\n",
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
self.open_project_home_page()
|
||||||
|
self.shutdown_app(force_exit=True)
|
||||||
|
elif "missing_keys" in error_message:
|
||||||
|
missing_versions = error_message.split(":")[1]
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"配置缺失 - {APP_NAME}",
|
||||||
|
f'\n云端缺失下载链接,可能云服务器正在维护,不影响其他版本下载。\n当前缺失版本:"{missing_versions}"\n',
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
else:
|
||||||
|
# 其他错误暂时只在debug模式下打印
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print(f"获取云端配置失败: {error_message}")
|
||||||
|
else:
|
||||||
|
self.cloud_config = data
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print("--- Cloud config fetched successfully ---")
|
||||||
|
print(json.dumps(data, indent=2))
|
||||||
|
|
||||||
|
def toggle_debug_mode(self, checked):
|
||||||
|
self.config["debug_mode"] = checked
|
||||||
|
save_config(self.config)
|
||||||
|
if checked:
|
||||||
|
self.start_logging()
|
||||||
|
else:
|
||||||
|
self.stop_logging()
|
||||||
|
|
||||||
|
def start_logging(self):
|
||||||
|
if self.logger is None:
|
||||||
|
try:
|
||||||
|
if os.path.exists(LOG_FILE):
|
||||||
|
os.remove(LOG_FILE)
|
||||||
|
# 保存原始的 stdout 和 stderr
|
||||||
|
self.original_stdout = sys.stdout
|
||||||
|
self.original_stderr = sys.stderr
|
||||||
|
# 创建 Logger 实例
|
||||||
|
self.logger = Logger(LOG_FILE, self.original_stdout)
|
||||||
|
sys.stdout = self.logger
|
||||||
|
sys.stderr = self.logger
|
||||||
|
print("--- Debug mode enabled ---")
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
QtWidgets.QMessageBox.critical(self, "错误", f"无法创建日志文件: {e}")
|
||||||
|
self.logger = None
|
||||||
|
|
||||||
|
def stop_logging(self):
|
||||||
|
if self.logger:
|
||||||
|
print("--- Debug mode disabled ---")
|
||||||
|
sys.stdout = self.original_stdout
|
||||||
|
sys.stderr = self.original_stderr
|
||||||
|
self.logger.close()
|
||||||
|
self.logger = None
|
||||||
|
|
||||||
|
def get_install_paths(self):
|
||||||
|
return {
|
||||||
|
game: os.path.join(self.selected_folder, info["install_path"])
|
||||||
|
for game, info in GAME_INFO.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def file_dialog(self):
|
||||||
|
self.selected_folder = QFileDialog.getExistingDirectory(
|
||||||
|
self, f"选择游戏所在【上级目录】 {APP_NAME}"
|
||||||
|
)
|
||||||
|
if not self.selected_folder:
|
||||||
|
QtWidgets.QMessageBox.warning(
|
||||||
|
self, f"通知 - {APP_NAME}", "\n未选择任何目录,请重新选择\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self.download_action()
|
||||||
|
|
||||||
|
def get_download_url(self) -> dict:
|
||||||
|
try:
|
||||||
|
if self.cloud_config:
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print("--- Using pre-fetched cloud config ---")
|
||||||
|
config_data = self.cloud_config
|
||||||
|
else:
|
||||||
|
# 如果没有预加载的配置,则同步获取
|
||||||
|
headers = {"User-Agent": UA}
|
||||||
|
response = requests.get(CONFIG_URL, headers=headers, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
config_data = response.json()
|
||||||
|
|
||||||
|
if not config_data:
|
||||||
|
raise ValueError("未能获取或解析配置数据")
|
||||||
|
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print(f"DEBUG: Parsed JSON data: {json.dumps(config_data, indent=2)}")
|
||||||
|
|
||||||
|
# 统一处理URL提取,确保返回扁平化的字典
|
||||||
|
urls = {}
|
||||||
|
for i in range(4):
|
||||||
|
key = f"vol.{i+1}.data"
|
||||||
|
if key in config_data and "url" in config_data[key]:
|
||||||
|
urls[f"vol{i+1}"] = config_data[key]["url"]
|
||||||
|
|
||||||
|
if "after.data" in config_data and "url" in config_data["after.data"]:
|
||||||
|
urls["after"] = config_data["after.data"]["url"]
|
||||||
|
|
||||||
|
# 检查是否成功提取了所有URL
|
||||||
|
if len(urls) != 5:
|
||||||
|
missing_keys_map = {
|
||||||
|
f"vol{i+1}": f"vol.{i+1}.data" for i in range(4)
|
||||||
|
}
|
||||||
|
missing_keys_map["after"] = "after.data"
|
||||||
|
|
||||||
|
extracted_keys = set(urls.keys())
|
||||||
|
all_keys = set(missing_keys_map.keys())
|
||||||
|
missing_simple_keys = all_keys - extracted_keys
|
||||||
|
|
||||||
|
missing_original_keys = [missing_keys_map[k] for k in missing_simple_keys]
|
||||||
|
raise ValueError(f"配置文件缺少必要的键: {', '.join(missing_original_keys)}")
|
||||||
|
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print(f"DEBUG: Extracted URLs: {urls}")
|
||||||
|
print("--- Finished getting download URL successfully ---")
|
||||||
|
return urls
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print(f"DEBUG: Extracted URLs: {urls}")
|
||||||
|
print("--- Finished getting download URL successfully ---")
|
||||||
|
return urls
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
status_code = e.response.status_code if e.response is not None else "未知"
|
||||||
|
try:
|
||||||
|
error_response = e.response.json() if e.response else {}
|
||||||
|
json_title = error_response.get("title", "无错误类型")
|
||||||
|
json_message = error_response.get("message", "无附加错误信息")
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
json_title = "配置文件异常,无法解析错误类型"
|
||||||
|
json_message = "配置文件异常,无法解析错误信息"
|
||||||
|
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print(f"ERROR: Failed to get download config due to RequestException: {e}")
|
||||||
|
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
f"\n下载配置获取失败\n\n【HTTP状态】:{status_code}\n【错误类型】:{json_title}\n【错误信息】:{json_message}\n",
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
except ValueError as e:
|
||||||
|
if self.debug_action.isChecked():
|
||||||
|
print(f"ERROR: Failed to parse download config due to ValueError: {e}")
|
||||||
|
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
f"\n配置文件格式异常\n\n【错误信息】:{e}\n",
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def download_setting(self, url, game_folder, game_version, _7z_path, plugin_path):
|
||||||
|
game_exe = {
|
||||||
|
game: os.path.join(
|
||||||
|
self.selected_folder, info["install_path"].split("/")[0], info["exe"]
|
||||||
|
)
|
||||||
|
for game, info in GAME_INFO.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
game_version not in game_exe
|
||||||
|
or not os.path.exists(game_exe[game_version])
|
||||||
|
or self.installed_status[game_version]
|
||||||
|
):
|
||||||
|
self.installed_status[game_version] = False
|
||||||
|
self.show_result()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.progress_window = ProgressWindow(self)
|
||||||
|
self.start_download_with_ip(self.optimized_ip, url, _7z_path, game_version, game_folder, plugin_path)
|
||||||
|
|
||||||
|
|
||||||
|
def on_optimization_and_hosts_finished(self, ip):
|
||||||
|
self.optimized_ip = ip
|
||||||
|
self.optimization_done = True
|
||||||
|
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
||||||
|
if self.optimizing_msg_box.isVisible():
|
||||||
|
self.optimizing_msg_box.accept()
|
||||||
|
self.optimizing_msg_box = None
|
||||||
|
|
||||||
|
if not ip:
|
||||||
|
QtWidgets.QMessageBox.warning(self, f"优选失败 - {APP_NAME}", "\n未能找到合适的Cloudflare IP,将使用默认网络进行下载。\n")
|
||||||
|
else:
|
||||||
|
if self.download_queue:
|
||||||
|
first_url = self.download_queue[0][0]
|
||||||
|
hostname = urlparse(first_url).hostname
|
||||||
|
if self.hosts_manager.apply_ip(hostname, ip):
|
||||||
|
QtWidgets.QMessageBox.information(self, f"成功 - {APP_NAME}", f"\n已将优选IP ({ip}) 应用到hosts文件。\n")
|
||||||
|
else:
|
||||||
|
QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", "\n修改hosts文件失败,请检查程序是否以管理员权限运行。\n")
|
||||||
|
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
def start_download_with_ip(self, preferred_ip, url, _7z_path, game_version, game_folder, plugin_path):
|
||||||
|
if preferred_ip:
|
||||||
|
print(f"已为 {game_version} 获取到优选IP: {preferred_ip}")
|
||||||
|
else:
|
||||||
|
print(f"未能为 {game_version} 获取优选IP,将使用默认线路。")
|
||||||
|
|
||||||
|
self.current_download_thread = DownloadThread(url, _7z_path, game_version, self)
|
||||||
|
self.current_download_thread.progress.connect(self.progress_window.update_progress)
|
||||||
|
self.current_download_thread.finished.connect(
|
||||||
|
lambda success, error: self.install_setting(
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
self.progress_window,
|
||||||
|
url,
|
||||||
|
game_folder,
|
||||||
|
game_version,
|
||||||
|
_7z_path,
|
||||||
|
plugin_path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.progress_window.stop_button.clicked.connect(self.current_download_thread.stop)
|
||||||
|
self.current_download_thread.start()
|
||||||
|
self.progress_window.exec()
|
||||||
|
|
||||||
|
def install_setting(
|
||||||
|
self,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
progress_window,
|
||||||
|
url,
|
||||||
|
game_folder,
|
||||||
|
game_version,
|
||||||
|
_7z_path,
|
||||||
|
plugin_path,
|
||||||
|
):
|
||||||
|
if progress_window.isVisible():
|
||||||
|
progress_window.reject()
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
print(f"--- Download Failed: {game_version} ---")
|
||||||
|
print(error)
|
||||||
|
print("------------------------------------")
|
||||||
|
msg_box = QtWidgets.QMessageBox(self)
|
||||||
|
msg_box.setWindowTitle(f"下载失败 - {APP_NAME}")
|
||||||
|
msg_box.setText(f"\n文件获取失败: {game_version}\n错误: {error}\n\n是否重试?")
|
||||||
|
|
||||||
|
retry_button = msg_box.addButton("重试", QtWidgets.QMessageBox.ButtonRole.YesRole)
|
||||||
|
next_button = msg_box.addButton("下一个", QtWidgets.QMessageBox.ButtonRole.NoRole)
|
||||||
|
end_button = msg_box.addButton("结束", QtWidgets.QMessageBox.ButtonRole.RejectRole)
|
||||||
|
|
||||||
|
msg_box.exec()
|
||||||
|
clicked_button = msg_box.clickedButton()
|
||||||
|
|
||||||
|
if clicked_button == retry_button:
|
||||||
|
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||||
|
elif clicked_button == next_button:
|
||||||
|
self.next_download_task()
|
||||||
|
else:
|
||||||
|
self.on_download_stopped()
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- Start Extraction in a new thread ---
|
||||||
|
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||||
|
|
||||||
|
self.extraction_thread = ExtractionThread(_7z_path, game_folder, plugin_path, game_version, self)
|
||||||
|
self.extraction_thread.finished.connect(self.on_extraction_finished)
|
||||||
|
self.extraction_thread.start()
|
||||||
|
|
||||||
|
def on_extraction_finished(self, success, error_message, game_version):
|
||||||
|
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
||||||
|
self.hash_msg_box.close()
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
QtWidgets.QMessageBox.critical(self, f"错误 - {APP_NAME}", error_message)
|
||||||
|
self.installed_status[game_version] = False
|
||||||
|
else:
|
||||||
|
self.installed_status[game_version] = True
|
||||||
|
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
def download_action(self):
|
||||||
|
# 询问用户是否使用Cloudflare加速
|
||||||
|
msg_box = QMessageBox(self)
|
||||||
|
msg_box.setWindowTitle(f"下载优化 - {APP_NAME}")
|
||||||
|
msg_box.setText("是否愿意通过Cloudflare加速来优化下载速度?\n\n这将临时修改系统的hosts文件,并需要管理员权限。")
|
||||||
|
msg_box.setIcon(QMessageBox.Icon.Question)
|
||||||
|
|
||||||
|
yes_button = msg_box.addButton("是,开启加速", QMessageBox.ButtonRole.YesRole)
|
||||||
|
no_button = msg_box.addButton("否,直接下载", QMessageBox.ButtonRole.NoRole)
|
||||||
|
|
||||||
|
msg_box.exec()
|
||||||
|
|
||||||
|
use_optimization = msg_box.clickedButton() == yes_button
|
||||||
|
|
||||||
|
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||||
|
|
||||||
|
install_paths = self.get_install_paths()
|
||||||
|
|
||||||
|
self.hash_thread = HashThread("pre", install_paths, PLUGIN_HASH, self.installed_status, self)
|
||||||
|
# 将用户选择传递给哈希完成后的处理函数
|
||||||
|
self.hash_thread.pre_finished.connect(lambda status: self.on_pre_hash_finished(status, use_optimization))
|
||||||
|
self.hash_thread.start()
|
||||||
|
|
||||||
|
def on_pre_hash_finished(self, updated_status, use_optimization):
|
||||||
|
self.installed_status = updated_status
|
||||||
|
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
||||||
|
self.hash_msg_box.accept()
|
||||||
|
self.hash_msg_box = None
|
||||||
|
|
||||||
|
config = self.get_download_url()
|
||||||
|
if not config:
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self, f"错误 - {APP_NAME}", "\n网络状态异常或服务器故障,请重试\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- 填充下载队列 ---
|
||||||
|
for i in range(1, 5):
|
||||||
|
game_version = f"NEKOPARA Vol.{i}"
|
||||||
|
if not self.installed_status.get(game_version, False):
|
||||||
|
url = config.get(f"vol{i}")
|
||||||
|
if not url: continue
|
||||||
|
game_folder = os.path.join(self.selected_folder, f"NEKOPARA Vol. {i}")
|
||||||
|
_7z_path = os.path.join(PLUGIN, f"vol.{i}.7z")
|
||||||
|
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||||
|
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||||
|
|
||||||
|
game_version = "NEKOPARA After"
|
||||||
|
if not self.installed_status.get(game_version, False):
|
||||||
|
url = config.get("after")
|
||||||
|
if url:
|
||||||
|
game_folder = os.path.join(self.selected_folder, "NEKOPARA After")
|
||||||
|
_7z_path = os.path.join(PLUGIN, "after.7z")
|
||||||
|
plugin_path = os.path.join(PLUGIN, GAME_INFO[game_version]["plugin_path"])
|
||||||
|
self.download_queue.append((url, game_folder, game_version, _7z_path, plugin_path))
|
||||||
|
|
||||||
|
if not self.download_queue:
|
||||||
|
self.after_hash_compare(PLUGIN_HASH)
|
||||||
|
return
|
||||||
|
|
||||||
|
if use_optimization and not self.optimization_done:
|
||||||
|
first_url = self.download_queue[0][0]
|
||||||
|
self.optimizing_msg_box = msgbox_frame(
|
||||||
|
f"通知 - {APP_NAME}",
|
||||||
|
"\n正在优选Cloudflare IP,请稍候...\n\n这可能需要5-10分钟,请耐心等待喵~"
|
||||||
|
)
|
||||||
|
# 我们不再提供“跳过”按钮,因为用户已经做出了选择
|
||||||
|
self.optimizing_msg_box.setStandardButtons(QMessageBox.StandardButton.NoButton)
|
||||||
|
self.optimizing_msg_box.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
self.optimizing_msg_box.open()
|
||||||
|
|
||||||
|
self.ip_optimizer_thread = IpOptimizerThread(first_url)
|
||||||
|
# 优选完成后,需要修改hosts并开始下载
|
||||||
|
self.ip_optimizer_thread.finished.connect(self.on_optimization_and_hosts_finished)
|
||||||
|
self.ip_optimizer_thread.start()
|
||||||
|
else:
|
||||||
|
# 如果用户选择不优化,或已经优化过,直接开始下载
|
||||||
|
self.next_download_task()
|
||||||
|
|
||||||
|
def next_download_task(self):
|
||||||
|
if not self.download_queue:
|
||||||
|
self.after_hash_compare(PLUGIN_HASH)
|
||||||
|
return
|
||||||
|
# 检查下载线程是否仍在运行,以避免在手动停止后立即开始下一个任务
|
||||||
|
if self.current_download_thread and self.current_download_thread.isRunning():
|
||||||
|
return
|
||||||
|
|
||||||
|
# 在开始下载前,确保hosts文件已修改(如果需要)
|
||||||
|
# 这里的逻辑保持不变,因为hosts文件应该在队列开始前就被修改了
|
||||||
|
url, game_folder, game_version, _7z_path, plugin_path = self.download_queue.popleft()
|
||||||
|
self.download_setting(url, game_folder, game_version, _7z_path, plugin_path)
|
||||||
|
|
||||||
|
def on_download_stopped(self):
|
||||||
|
"""当用户点击停止按钮或选择结束时调用的槽函数"""
|
||||||
|
# 停止IP优选线程
|
||||||
|
if hasattr(self, 'ip_optimizer_thread') and self.ip_optimizer_thread and self.ip_optimizer_thread.isRunning():
|
||||||
|
self.ip_optimizer_thread.stop()
|
||||||
|
self.ip_optimizer_thread.wait()
|
||||||
|
if hasattr(self, 'optimizing_msg_box') and self.optimizing_msg_box:
|
||||||
|
if self.optimizing_msg_box.isVisible():
|
||||||
|
self.optimizing_msg_box.accept()
|
||||||
|
self.optimizing_msg_box = None
|
||||||
|
|
||||||
|
# 停止当前可能仍在运行的下载线程
|
||||||
|
if self.current_download_thread and self.current_download_thread.isRunning():
|
||||||
|
self.current_download_thread.stop()
|
||||||
|
self.current_download_thread.wait() # 等待线程完全终止
|
||||||
|
|
||||||
|
# 清空下载队列,因为用户决定停止
|
||||||
|
self.download_queue.clear()
|
||||||
|
|
||||||
|
# 确保进度窗口已关闭
|
||||||
|
if hasattr(self, 'progress_window') and self.progress_window.isVisible():
|
||||||
|
self.progress_window.reject()
|
||||||
|
|
||||||
|
# 可以在这里决定是否立即进行哈希比较或显示结果
|
||||||
|
print("下载已全部停止。")
|
||||||
|
self.setEnabled(True) # 恢复主窗口交互
|
||||||
|
self.show_result()
|
||||||
|
|
||||||
|
def after_hash_compare(self, plugin_hash):
|
||||||
|
self.hash_msg_box = self.hash_manager.hash_pop_window()
|
||||||
|
|
||||||
|
install_paths = self.get_install_paths()
|
||||||
|
|
||||||
|
self.hash_thread = HashThread("after", install_paths, plugin_hash, self.installed_status, self)
|
||||||
|
self.hash_thread.after_finished.connect(self.on_after_hash_finished)
|
||||||
|
self.hash_thread.start()
|
||||||
|
|
||||||
|
def on_after_hash_finished(self, result):
|
||||||
|
if self.hash_msg_box and self.hash_msg_box.isVisible():
|
||||||
|
self.hash_msg_box.close()
|
||||||
|
|
||||||
|
if not result["passed"]:
|
||||||
|
game = result.get("game", "未知游戏")
|
||||||
|
message = result.get("message", "发生未知错误。")
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"文件校验失败 - {APP_NAME}",
|
||||||
|
message,
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.exec()
|
||||||
|
|
||||||
|
self.show_result()
|
||||||
|
|
||||||
|
def show_result(self):
|
||||||
|
installed_version = "\n".join(
|
||||||
|
[i for i in self.installed_status if self.installed_status[i]]
|
||||||
|
)
|
||||||
|
failed_ver = "\n".join(
|
||||||
|
[i for i in self.installed_status if not self.installed_status[i]]
|
||||||
|
)
|
||||||
|
QtWidgets.QMessageBox.information(
|
||||||
|
self,
|
||||||
|
f"完成 - {APP_NAME}",
|
||||||
|
f"\n安装结果:\n安装成功数:{len(installed_version.splitlines())} 安装失败数:{len(failed_ver.splitlines())}\n"
|
||||||
|
f"安装成功的版本:\n{installed_version}\n尚未持有或未使用本工具安装补丁的版本:\n{failed_ver}\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_about_dialog(self):
|
||||||
|
"""显示关于对话框"""
|
||||||
|
about_text = f"""
|
||||||
|
<p><b>{APP_NAME} v{APP_VERSION}</b></p>
|
||||||
|
<p>原作: <a href="https://github.com/Yanam1Anna">Yanam1Anna</a></p>
|
||||||
|
<p>此应用根据 <a href="https://github.com/hyb-oyqq/FRAISEMOE2-Installer/blob/master/LICENSE">GPL-3.0 许可证</a> 授权。</p>
|
||||||
|
"""
|
||||||
|
msg_box = msgbox_frame(
|
||||||
|
f"关于 - {APP_NAME}",
|
||||||
|
about_text,
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||||
|
)
|
||||||
|
msg_box.setTextFormat(Qt.TextFormat.RichText) # 启用富文本
|
||||||
|
msg_box.exec()
|
||||||
|
|
||||||
|
def open_project_home_page(self):
|
||||||
|
"""打开项目主页"""
|
||||||
|
webbrowser.open("https://github.com/hyb-oyqq/FRAISEMOE-Addons-Installer-NEXT")
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self.shutdown_app(event)
|
||||||
|
|
||||||
|
def shutdown_app(self, event=None, force_exit=False):
|
||||||
|
self.hosts_manager.restore() # 恢复hosts文件
|
||||||
|
self.stop_logging() # 确保在退出时停止日志记录
|
||||||
|
|
||||||
|
if not force_exit:
|
||||||
|
reply = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"退出程序",
|
||||||
|
"\n是否确定退出?\n",
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No,
|
||||||
|
QtWidgets.QMessageBox.StandardButton.No,
|
||||||
|
)
|
||||||
|
if reply != QtWidgets.QMessageBox.StandardButton.Yes:
|
||||||
|
if event:
|
||||||
|
event.ignore()
|
||||||
|
return
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.current_download_thread
|
||||||
|
and self.current_download_thread.isRunning()
|
||||||
|
):
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
"\n当前有下载任务正在进行,完成后再试\n",
|
||||||
|
)
|
||||||
|
if event:
|
||||||
|
event.ignore()
|
||||||
|
return
|
||||||
|
|
||||||
|
if os.path.exists(PLUGIN):
|
||||||
|
for attempt in range(3):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(PLUGIN)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if attempt == 2:
|
||||||
|
QtWidgets.QMessageBox.critical(
|
||||||
|
self,
|
||||||
|
f"错误 - {APP_NAME}",
|
||||||
|
f"\n清理缓存失败\n\n【错误信息】:{e}\n",
|
||||||
|
)
|
||||||
|
if event:
|
||||||
|
event.accept()
|
||||||
|
sys.exit(1)
|
||||||
|
if event:
|
||||||
|
event.accept()
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 264 KiB |
BIN
source/resources/images/After/voaf_ga01.jpg
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
source/resources/images/After/voaf_ga02.jpg
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
source/resources/images/BG/bg1.jpg
Normal file
|
After Width: | Height: | Size: 571 KiB |
BIN
source/resources/images/BG/bg2.jpg
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
source/resources/images/BG/bg3.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
source/resources/images/BG/bg4.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
source/resources/images/BG/menubg.jpg
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
source/resources/images/BTN/exit.bmp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
source/resources/images/BTN/start_install.bmp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
source/resources/images/ICO/icon.ico
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
source/resources/images/ICO/icon.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
source/resources/images/LOGO/gl_head_logo_jp.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
source/resources/images/LOGO/vo01_logo.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
source/resources/images/LOGO/vo02_logo.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
source/resources/images/LOGO/vo03_logo.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
source/resources/images/LOGO/vo04_logo.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
source/resources/images/LOGO/voaf_logo.png
Normal file
|
After Width: | Height: | Size: 327 KiB |
BIN
source/resources/images/vol4/vo04_ga01.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
source/resources/images/vol4/vo04_ga05.jpg
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
source/resources/images/vol4/vo04_ga06.jpg
Normal file
|
After Width: | Height: | Size: 197 KiB |
BIN
source/resources/images/vol4/vo04_ga07.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
@@ -1,4 +1,4 @@
|
|||||||
from pic_data import img_data
|
from data.pic_data import img_data
|
||||||
from PySide6.QtGui import QPixmap
|
from PySide6.QtGui import QPixmap
|
||||||
import base64
|
import base64
|
||||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
||||||
18
source/utils/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from .logger import Logger
|
||||||
|
from .helpers import (
|
||||||
|
load_base64_image, HashManager, AdminPrivileges, msgbox_frame,
|
||||||
|
load_config, save_config, HostsManager, censor_url, resource_path
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Logger',
|
||||||
|
'load_base64_image',
|
||||||
|
'HashManager',
|
||||||
|
'AdminPrivileges',
|
||||||
|
'msgbox_frame',
|
||||||
|
'load_config',
|
||||||
|
'save_config',
|
||||||
|
'HostsManager',
|
||||||
|
'censor_url',
|
||||||
|
'resource_path'
|
||||||
|
]
|
||||||
@@ -9,8 +9,8 @@ import psutil
|
|||||||
from PySide6 import QtCore, QtWidgets
|
from PySide6 import QtCore, QtWidgets
|
||||||
import re
|
import re
|
||||||
from PySide6.QtGui import QIcon, QPixmap
|
from PySide6.QtGui import QIcon, QPixmap
|
||||||
from pic_data import img_data
|
from data.pic_data import img_data
|
||||||
from config import APP_NAME, CONFIG_FILE
|
from data.config import APP_NAME, CONFIG_FILE
|
||||||
|
|
||||||
def resource_path(relative_path):
|
def resource_path(relative_path):
|
||||||
"""获取资源的绝对路径,适用于开发环境和PyInstaller打包环境"""
|
"""获取资源的绝对路径,适用于开发环境和PyInstaller打包环境"""
|
||||||
@@ -19,7 +19,18 @@ def resource_path(relative_path):
|
|||||||
base_path = getattr(sys, '_MEIPASS', os.path.dirname(sys.executable))
|
base_path = getattr(sys, '_MEIPASS', os.path.dirname(sys.executable))
|
||||||
else:
|
else:
|
||||||
# 在开发环境中运行
|
# 在开发环境中运行
|
||||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
base_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
# 现在base_path是项目根目录,我们需要进入source目录
|
||||||
|
base_path = os.path.join(base_path, 'source')
|
||||||
|
|
||||||
|
# 处理特殊情况
|
||||||
|
if relative_path == "aria2c.exe":
|
||||||
|
return os.path.join(base_path, 'bin', relative_path)
|
||||||
|
elif relative_path == "cfst.exe":
|
||||||
|
return os.path.join(base_path, 'bin', relative_path)
|
||||||
|
elif relative_path == "ip.txt" or relative_path == "ipv6.txt":
|
||||||
|
return os.path.join(base_path, 'resources', 'data', relative_path)
|
||||||
|
|
||||||
return os.path.join(base_path, relative_path)
|
return os.path.join(base_path, relative_path)
|
||||||
|
|
||||||
def load_base64_image(base64_str):
|
def load_base64_image(base64_str):
|
||||||
@@ -224,6 +235,7 @@ class HostsManager:
|
|||||||
self.backup_path = os.path.join(os.path.dirname(self.hosts_path), f'hosts.bak.{APP_NAME}')
|
self.backup_path = os.path.join(os.path.dirname(self.hosts_path), f'hosts.bak.{APP_NAME}')
|
||||||
self.original_content = None
|
self.original_content = None
|
||||||
self.modified = False
|
self.modified = False
|
||||||
|
self.modified_hostnames = set() # 跟踪被修改的主机名
|
||||||
|
|
||||||
def backup(self):
|
def backup(self):
|
||||||
if not AdminPrivileges().is_admin():
|
if not AdminPrivileges().is_admin():
|
||||||
@@ -241,6 +253,48 @@ class HostsManager:
|
|||||||
msg_box = msgbox_frame(f"错误 - {APP_NAME}", f"\n无法备份hosts文件,请检查权限。\n\n【错误信息】:{e}\n", QtWidgets.QMessageBox.StandardButton.Ok)
|
msg_box = msgbox_frame(f"错误 - {APP_NAME}", f"\n无法备份hosts文件,请检查权限。\n\n【错误信息】:{e}\n", QtWidgets.QMessageBox.StandardButton.Ok)
|
||||||
msg_box.exec()
|
msg_box.exec()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def clean_hostname_entries(self, hostname):
|
||||||
|
"""清理hosts文件中指定域名的所有记录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: 要清理的域名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 清理是否成功
|
||||||
|
"""
|
||||||
|
if not self.original_content:
|
||||||
|
if not self.backup():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 确保original_content不为None
|
||||||
|
if not self.original_content:
|
||||||
|
print("无法读取hosts文件内容,操作中止。")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not AdminPrivileges().is_admin():
|
||||||
|
print("需要管理员权限来修改hosts文件。")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
lines = self.original_content.splitlines()
|
||||||
|
new_lines = [line for line in lines if hostname not in line]
|
||||||
|
|
||||||
|
# 如果没有变化,不需要写入
|
||||||
|
if len(new_lines) == len(lines):
|
||||||
|
print(f"Hosts文件中没有找到 {hostname} 的记录")
|
||||||
|
return True
|
||||||
|
|
||||||
|
with open(self.hosts_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('\n'.join(new_lines))
|
||||||
|
|
||||||
|
# 更新原始内容
|
||||||
|
self.original_content = '\n'.join(new_lines)
|
||||||
|
print(f"已从hosts文件中清理 {hostname} 的记录")
|
||||||
|
return True
|
||||||
|
except IOError as e:
|
||||||
|
print(f"清理hosts文件失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def apply_ip(self, hostname, ip_address):
|
def apply_ip(self, hostname, ip_address):
|
||||||
if not self.original_content:
|
if not self.original_content:
|
||||||
@@ -256,17 +310,23 @@ class HostsManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lines = self.original_content.splitlines()
|
# 首先清理已有的同域名记录
|
||||||
new_lines = [line for line in lines if not (hostname in line and line.strip().startswith(ip_address))]
|
self.clean_hostname_entries(hostname)
|
||||||
|
|
||||||
|
# 然后添加新记录
|
||||||
|
lines = self.original_content.splitlines()
|
||||||
new_entry = f"{ip_address}\t{hostname}"
|
new_entry = f"{ip_address}\t{hostname}"
|
||||||
new_lines.append(f"\n# Added by {APP_NAME}")
|
lines.append(f"\n# Added by {APP_NAME}")
|
||||||
new_lines.append(new_entry)
|
lines.append(new_entry)
|
||||||
|
|
||||||
with open(self.hosts_path, 'w', encoding='utf-8') as f:
|
with open(self.hosts_path, 'w', encoding='utf-8') as f:
|
||||||
f.write('\n'.join(new_lines))
|
f.write('\n'.join(lines))
|
||||||
|
|
||||||
|
# 更新原始内容
|
||||||
|
self.original_content = '\n'.join(lines)
|
||||||
self.modified = True
|
self.modified = True
|
||||||
|
# 记录被修改的主机名,用于最终清理
|
||||||
|
self.modified_hostnames.add(hostname)
|
||||||
print(f"Hosts文件已更新: {new_entry}")
|
print(f"Hosts文件已更新: {new_entry}")
|
||||||
return True
|
return True
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
@@ -275,6 +335,55 @@ class HostsManager:
|
|||||||
msg_box.exec()
|
msg_box.exec()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def check_and_clean_all_entries(self):
|
||||||
|
"""检查并清理所有由本应用程序添加的hosts记录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 清理是否成功
|
||||||
|
"""
|
||||||
|
if not AdminPrivileges().is_admin():
|
||||||
|
print("需要管理员权限来检查和清理hosts文件。")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 读取当前hosts文件内容
|
||||||
|
with open(self.hosts_path, 'r', encoding='utf-8') as f:
|
||||||
|
current_content = f.read()
|
||||||
|
|
||||||
|
lines = current_content.splitlines()
|
||||||
|
new_lines = []
|
||||||
|
skip_next = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
# 如果上一行是我们的注释标记,跳过当前行
|
||||||
|
if skip_next:
|
||||||
|
skip_next = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否是我们添加的注释行
|
||||||
|
if f"# Added by {APP_NAME}" in line:
|
||||||
|
skip_next = True # 跳过下一行(实际的hosts记录)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 保留其他所有行
|
||||||
|
new_lines.append(line)
|
||||||
|
|
||||||
|
# 检查是否有变化
|
||||||
|
if len(new_lines) == len(lines):
|
||||||
|
print("Hosts文件中没有找到由本应用添加的记录")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 写回清理后的内容
|
||||||
|
with open(self.hosts_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('\n'.join(new_lines))
|
||||||
|
|
||||||
|
print(f"已清理所有由 {APP_NAME} 添加的hosts记录")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except IOError as e:
|
||||||
|
print(f"检查和清理hosts文件失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def restore(self):
|
def restore(self):
|
||||||
if not self.modified:
|
if not self.modified:
|
||||||
if os.path.exists(self.backup_path):
|
if os.path.exists(self.backup_path):
|
||||||
@@ -282,6 +391,8 @@ class HostsManager:
|
|||||||
os.remove(self.backup_path)
|
os.remove(self.backup_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
# 即使没有修改过,也检查一次是否有残留
|
||||||
|
self.check_and_clean_all_entries()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not AdminPrivileges().is_admin():
|
if not AdminPrivileges().is_admin():
|
||||||
@@ -299,6 +410,8 @@ class HostsManager:
|
|||||||
os.remove(self.backup_path)
|
os.remove(self.backup_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
# 恢复后再检查一次是否有残留
|
||||||
|
self.check_and_clean_all_entries()
|
||||||
return True
|
return True
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
print(f"从内存恢复hosts文件失败: {e}")
|
print(f"从内存恢复hosts文件失败: {e}")
|
||||||
@@ -309,6 +422,8 @@ class HostsManager:
|
|||||||
def restore_from_backup_file(self):
|
def restore_from_backup_file(self):
|
||||||
if not os.path.exists(self.backup_path):
|
if not os.path.exists(self.backup_path):
|
||||||
print("未找到hosts备份文件,无法恢复。")
|
print("未找到hosts备份文件,无法恢复。")
|
||||||
|
# 即使没有备份文件,也尝试清理可能的残留
|
||||||
|
self.check_and_clean_all_entries()
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
with open(self.backup_path, 'r', encoding='utf-8') as bf:
|
with open(self.backup_path, 'r', encoding='utf-8') as bf:
|
||||||
@@ -318,11 +433,15 @@ class HostsManager:
|
|||||||
os.remove(self.backup_path)
|
os.remove(self.backup_path)
|
||||||
self.modified = False
|
self.modified = False
|
||||||
print("Hosts文件已从备份文件恢复。")
|
print("Hosts文件已从备份文件恢复。")
|
||||||
|
# 恢复后再检查一次是否有残留
|
||||||
|
self.check_and_clean_all_entries()
|
||||||
return True
|
return True
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
print(f"从备份文件恢复hosts失败: {e}")
|
print(f"从备份文件恢复hosts失败: {e}")
|
||||||
msg_box = msgbox_frame(f"警告 - {APP_NAME}", f"\n自动恢复hosts文件失败,请手动从 {self.backup_path} 恢复。\n\n【错误信息】:{e}\n", QtWidgets.QMessageBox.StandardButton.Ok)
|
msg_box = msgbox_frame(f"警告 - {APP_NAME}", f"\n自动恢复hosts文件失败,请手动从 {self.backup_path} 恢复。\n\n【错误信息】:{e}\n", QtWidgets.QMessageBox.StandardButton.Ok)
|
||||||
msg_box.exec()
|
msg_box.exec()
|
||||||
|
# 尽管恢复失败,仍然尝试清理可能的残留
|
||||||
|
self.check_and_clean_all_entries()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def censor_url(text):
|
def censor_url(text):
|
||||||
19
source/utils/logger.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from .helpers import censor_url
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self, filename, stream):
|
||||||
|
self.terminal = stream
|
||||||
|
self.log = open(filename, "w", encoding="utf-8")
|
||||||
|
|
||||||
|
def write(self, message):
|
||||||
|
censored_message = censor_url(message)
|
||||||
|
self.terminal.write(censored_message)
|
||||||
|
self.log.write(censored_message)
|
||||||
|
self.flush()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
self.terminal.flush()
|
||||||
|
self.log.flush()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.log.close()
|
||||||
14
source/workers/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from .hash_thread import HashThread
|
||||||
|
from .extraction_thread import ExtractionThread
|
||||||
|
from .config_fetch_thread import ConfigFetchThread
|
||||||
|
from .ip_optimizer import IpOptimizerThread
|
||||||
|
from .download import DownloadThread, ProgressWindow
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'IpOptimizerThread',
|
||||||
|
'HashThread',
|
||||||
|
'ExtractionThread',
|
||||||
|
'ConfigFetchThread',
|
||||||
|
'DownloadThread',
|
||||||
|
'ProgressWindow'
|
||||||
|
]
|
||||||
52
source/workers/config_fetch_thread.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from PySide6.QtCore import QThread, Signal
|
||||||
|
|
||||||
|
class ConfigFetchThread(QThread):
|
||||||
|
finished = Signal(object, str) # data, error_message
|
||||||
|
|
||||||
|
def __init__(self, url, headers, debug_mode=False, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.url = url
|
||||||
|
self.headers = headers
|
||||||
|
self.debug_mode = debug_mode
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
if self.debug_mode:
|
||||||
|
print("--- Starting to fetch cloud config ---")
|
||||||
|
print(f"DEBUG: Requesting URL: {self.url}")
|
||||||
|
print(f"DEBUG: Using Headers: {self.headers}")
|
||||||
|
|
||||||
|
response = requests.get(self.url, headers=self.headers, timeout=10)
|
||||||
|
|
||||||
|
if self.debug_mode:
|
||||||
|
print(f"DEBUG: Response Status Code: {response.status_code}")
|
||||||
|
print(f"DEBUG: Response Headers: {response.headers}")
|
||||||
|
print(f"DEBUG: Response Text: {response.text}")
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 首先,总是尝试解析JSON
|
||||||
|
config_data = response.json()
|
||||||
|
|
||||||
|
# 检查是否是要求更新的错误信息
|
||||||
|
if config_data.get("message") == "请使用最新版本的FRAISEMOE Addons Installer NEXT进行下载安装":
|
||||||
|
self.finished.emit(None, "update_required")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查是否是有效的配置文件
|
||||||
|
required_keys = [f"vol.{i+1}.data" for i in range(4)] + ["after.data"]
|
||||||
|
missing_keys = [key for key in required_keys if key not in config_data]
|
||||||
|
if missing_keys:
|
||||||
|
self.finished.emit(None, f"missing_keys:{','.join(missing_keys)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.finished.emit(config_data, "")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.finished.emit(None, f"网络请求失败: {e}")
|
||||||
|
except (ValueError, json.JSONDecodeError) as e:
|
||||||
|
self.finished.emit(None, f"JSON解析失败: {e}")
|
||||||
|
finally:
|
||||||
|
if self.debug_mode:
|
||||||
|
print("--- Finished fetching cloud config ---")
|
||||||
@@ -7,7 +7,7 @@ from PySide6 import QtCore, QtWidgets
|
|||||||
from PySide6.QtCore import (Qt, Signal, QThread, QTimer)
|
from PySide6.QtCore import (Qt, Signal, QThread, QTimer)
|
||||||
from PySide6.QtWidgets import (QLabel, QProgressBar, QVBoxLayout, QDialog)
|
from PySide6.QtWidgets import (QLabel, QProgressBar, QVBoxLayout, QDialog)
|
||||||
from utils import resource_path
|
from utils import resource_path
|
||||||
from config import APP_NAME, UA
|
from data.config import APP_NAME, UA
|
||||||
|
|
||||||
# 下载线程类
|
# 下载线程类
|
||||||
class DownloadThread(QThread):
|
class DownloadThread(QThread):
|
||||||
31
source/workers/extraction_thread.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import py7zr
|
||||||
|
from PySide6.QtCore import QThread, Signal
|
||||||
|
from data.config import PLUGIN, GAME_INFO
|
||||||
|
|
||||||
|
class ExtractionThread(QThread):
|
||||||
|
finished = Signal(bool, str, str) # success, error_message, game_version
|
||||||
|
|
||||||
|
def __init__(self, _7z_path, game_folder, plugin_path, game_version, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._7z_path = _7z_path
|
||||||
|
self.game_folder = game_folder
|
||||||
|
self.plugin_path = plugin_path
|
||||||
|
self.game_version = game_version
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
with py7zr.SevenZipFile(self._7z_path, mode="r") as archive:
|
||||||
|
archive.extractall(path=PLUGIN)
|
||||||
|
|
||||||
|
os.makedirs(self.game_folder, exist_ok=True)
|
||||||
|
shutil.copy(self.plugin_path, self.game_folder)
|
||||||
|
|
||||||
|
if self.game_version == "NEKOPARA After":
|
||||||
|
sig_path = os.path.join(PLUGIN, GAME_INFO[self.game_version]["sig_path"])
|
||||||
|
shutil.copy(sig_path, self.game_folder)
|
||||||
|
|
||||||
|
self.finished.emit(True, "", self.game_version)
|
||||||
|
except (py7zr.Bad7zFile, FileNotFoundError, Exception) as e:
|
||||||
|
self.finished.emit(False, f"\n文件操作失败,请重试\n\n【错误信息】:{e}\n", self.game_version)
|
||||||
28
source/workers/hash_thread.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from PySide6.QtCore import QThread, Signal
|
||||||
|
from utils import HashManager
|
||||||
|
from data.config import BLOCK_SIZE
|
||||||
|
|
||||||
|
class HashThread(QThread):
|
||||||
|
pre_finished = Signal(dict)
|
||||||
|
after_finished = Signal(dict)
|
||||||
|
|
||||||
|
def __init__(self, mode, install_paths, plugin_hash, installed_status, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.mode = mode
|
||||||
|
self.install_paths = install_paths
|
||||||
|
self.plugin_hash = plugin_hash
|
||||||
|
self.installed_status = installed_status
|
||||||
|
# 每个线程都应该有自己的HashManager实例
|
||||||
|
self.hash_manager = HashManager(BLOCK_SIZE)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.mode == "pre":
|
||||||
|
updated_status = self.hash_manager.cfg_pre_hash_compare(
|
||||||
|
self.install_paths, self.plugin_hash, self.installed_status
|
||||||
|
)
|
||||||
|
self.pre_finished.emit(updated_status)
|
||||||
|
elif self.mode == "after":
|
||||||
|
result = self.hash_manager.cfg_after_hash_compare(
|
||||||
|
self.install_paths, self.plugin_hash, self.installed_status
|
||||||
|
)
|
||||||
|
self.after_finished.emit(result)
|
||||||
@@ -5,6 +5,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from PySide6.QtCore import QThread, Signal
|
||||||
from utils import resource_path
|
from utils import resource_path
|
||||||
|
|
||||||
class IpOptimizer:
|
class IpOptimizer:
|
||||||
@@ -28,13 +29,15 @@ class IpOptimizer:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
ip_txt_path = resource_path("ip.txt")
|
ip_txt_path = resource_path("ip.txt")
|
||||||
|
|
||||||
|
# 正确的参数设置,根据cfst帮助文档
|
||||||
command = [
|
command = [
|
||||||
cst_path,
|
cst_path,
|
||||||
"-p", "1",
|
"-n", "500", # 延迟测速线程数 (默认200)
|
||||||
"-o", "",
|
"-p", "1", # 显示结果数量 (默认10个)
|
||||||
"-url", url,
|
"-url", url, # 指定测速地址
|
||||||
"-f", ip_txt_path,
|
"-f", ip_txt_path, # IP文件
|
||||||
"-dd",
|
"-dd", # 禁用下载测速,按延迟排序
|
||||||
]
|
]
|
||||||
|
|
||||||
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
||||||
@@ -53,17 +56,13 @@ class IpOptimizer:
|
|||||||
bufsize=0
|
bufsize=0
|
||||||
)
|
)
|
||||||
|
|
||||||
# 立即向 stdin 发送换行符,以便程序在 Windows 下正常退出
|
# 更新正则表达式以匹配cfst输出中的IP格式
|
||||||
if self.process.stdin:
|
# 匹配格式: IP地址在行首,后面跟着一些数字和文本
|
||||||
try:
|
ip_pattern = re.compile(r'^(\d+\.\d+\.\d+\.\d+)\s+.*')
|
||||||
self.process.stdin.write('\n')
|
|
||||||
self.process.stdin.flush()
|
# 标记是否已经找到结果表头和完成标记
|
||||||
except:
|
found_header = False
|
||||||
pass
|
found_completion = False
|
||||||
finally:
|
|
||||||
self.process.stdin.close()
|
|
||||||
|
|
||||||
ip_pattern = re.compile(r'^\s*([\d\.]+)\s+\d+\s+\d+\s+[\d\.]+%?\s+[\d\.]+\s+[\d\.]+\s+.*$')
|
|
||||||
|
|
||||||
stdout = self.process.stdout
|
stdout = self.process.stdout
|
||||||
if not stdout:
|
if not stdout:
|
||||||
@@ -72,7 +71,7 @@ class IpOptimizer:
|
|||||||
|
|
||||||
optimal_ip = None
|
optimal_ip = None
|
||||||
timeout_counter = 0
|
timeout_counter = 0
|
||||||
max_timeout = 60
|
max_timeout = 300 # 增加超时时间到5分钟
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if self.process.poll() is not None:
|
if self.process.poll() is not None:
|
||||||
@@ -97,16 +96,47 @@ class IpOptimizer:
|
|||||||
cleaned_line = line.strip()
|
cleaned_line = line.strip()
|
||||||
if cleaned_line:
|
if cleaned_line:
|
||||||
print(cleaned_line)
|
print(cleaned_line)
|
||||||
match = ip_pattern.match(cleaned_line)
|
|
||||||
if match:
|
# 检测结果表头
|
||||||
optimal_ip = match.group(1)
|
if "IP 地址" in cleaned_line and "平均延迟" in cleaned_line:
|
||||||
print(f"找到最优 IP: {optimal_ip}, 正在终止测速进程...")
|
print("检测到IP结果表头,准备获取IP地址...")
|
||||||
break
|
found_header = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检测完成标记
|
||||||
|
if "完整测速结果已写入" in cleaned_line or "按下 回车键 或 Ctrl+C 退出" in cleaned_line:
|
||||||
|
print("检测到测速完成信息")
|
||||||
|
found_completion = True
|
||||||
|
|
||||||
|
# 如果已经找到了IP,可以退出了
|
||||||
|
if optimal_ip:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 已找到表头后,尝试匹配IP地址行
|
||||||
|
if found_header:
|
||||||
|
match = ip_pattern.search(cleaned_line)
|
||||||
|
if match and not optimal_ip: # 只保存第一个匹配的IP(最优IP)
|
||||||
|
optimal_ip = match.group(1)
|
||||||
|
print(f"找到最优 IP: {optimal_ip}")
|
||||||
|
|
||||||
|
# 如果已经看到完成标记,可以退出了
|
||||||
|
if found_completion:
|
||||||
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"读取输出时发生错误: {e}")
|
print(f"读取输出时发生错误: {e}")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# 确保完全读取输出后再发送退出信号
|
||||||
|
if self.process and self.process.poll() is None:
|
||||||
|
try:
|
||||||
|
if self.process.stdin and not self.process.stdin.closed:
|
||||||
|
print("发送退出信号...")
|
||||||
|
self.process.stdin.write('\n')
|
||||||
|
self.process.stdin.flush()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
print("--- CloudflareSpeedTest 执行结束 ---")
|
print("--- CloudflareSpeedTest 执行结束 ---")
|
||||||
@@ -135,6 +165,24 @@ class IpOptimizer:
|
|||||||
self.process.wait()
|
self.process.wait()
|
||||||
print("CloudflareSpeedTest 进程已终止。")
|
print("CloudflareSpeedTest 进程已终止。")
|
||||||
|
|
||||||
|
|
||||||
|
class IpOptimizerThread(QThread):
|
||||||
|
"""用于在后台线程中运行IP优化的类"""
|
||||||
|
finished = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, url, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.url = url
|
||||||
|
self.optimizer = IpOptimizer()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
optimal_ip = self.optimizer.get_optimal_ip(self.url)
|
||||||
|
self.finished.emit(optimal_ip if optimal_ip else "")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.optimizer.stop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# 用于直接测试此模块
|
# 用于直接测试此模块
|
||||||
test_url = "https://speed.cloudflare.com/__down?during=download&bytes=104857600"
|
test_url = "https://speed.cloudflare.com/__down?during=download&bytes=104857600"
|
||||||
@@ -143,4 +191,4 @@ if __name__ == '__main__':
|
|||||||
if ip:
|
if ip:
|
||||||
print(f"为 {test_url} 找到的最优 IP 是: {ip}")
|
print(f"为 {test_url} 找到的最优 IP 是: {ip}")
|
||||||
else:
|
else:
|
||||||
print(f"未能为 {test_url} 找到最优 IP。")
|
print(f"未能为 {test_url} 找到最优 IP。")
|
||||||