Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-Client Detection and NoLeagueClientDetected Exception Handling #39

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
94 changes: 89 additions & 5 deletions lcu_driver/connector.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import asyncio
import logging
import time
import unicodedata
import pandas as pd
import shutil, psutil
from wcwidth import wcswidth
from abc import ABC, abstractmethod

from .connection import Connection
from .events.managers import ConnectorEventManager, WebsocketEventManager
from .exceptions import NoLeagueClientDetected
from .utils import _return_ux_process

logger = logging.getLogger('lcu-driver')
Expand Down Expand Up @@ -53,12 +58,91 @@ def start(self) -> None:
:rtype: None
"""
try:
def count_nonASCII(s: str): #统计一个字符串中占用命令行2个宽度单位的字符个数(Count the number of characters that take up 2 width unit in CMD)
return sum([unicodedata.east_asian_width(character) in ("F", "W") for character in list(str(s))])

def format_df(df: pd.DataFrame): #按照每列最长字符串的命令行宽度加上2,再根据每个数据的中文字符数量决定最终格式化输出的字符串宽度(Get the width of the longest string of each column, add it by 2, and substract it by the number of each cell string's Chinese characters to get the final width for each cell to print using `format` function)
df = df.reset_index(drop = True) #这一步至关重要,因为下面的操作前提是行号是默认的(This step is crucial, for the following operations are based on the dataframe with the default row index)
maxLens = {}
maxWidth = shutil.get_terminal_size()[0]
fields = df.columns.tolist()
for field in fields:
maxLens[field] = max(max(map(lambda x: wcswidth(str(x)), df[field])), wcswidth(str(field))) + 2
if sum(maxLens.values()) + 2 * (len(fields) - 1) > maxWidth: #因为输出的时候,相邻两列之间需要有两个空格分隔,所以在计算总宽度的时候必须算上这些空格的宽度(Because two spaces are used between each pair of columns, the width they take up must be taken into consideration)
print("单行数据字符串输出宽度超过当前终端窗口宽度!是否继续?(输入任意键继续,否则直接打印该数据框。)\nThe output width of each record string exceeds the current width of the terminal window! Continue? (Input anything to continue, or null to directly print this dataframe.)")
if input() == "":
#print(df)
result = str(df)
return (result, maxLens)
result = ""
for i in range(df.shape[1]):
field = fields[i]
tmp = "{0:^{w}}".format(field, w = maxLens[str(field)] - count_nonASCII(str(field)))
result += tmp
#print(tmp, end = "")
if i != df.shape[1] - 1:
result += " "
#print(" ", end = "")
result += "\n"
#print()
for i in range(df.shape[0]):
for j in range(df.shape[1]):
field = fields[j]
cell = df[field][i]
tmp = "{0:^{w}}".format(cell, w = maxLens[field] - count_nonASCII(str(cell)))
result += tmp
#print(tmp, end = "")
if j != df.shape[1] - 1:
result += " "
#print(" ", end = "")
if i != df.shape[0] - 1:
result += "\n"
#print() #注意这里的缩进和上一行不同(Note that here the indentation is different from the last line)
return (result, maxLens)

def wrapper():
process = next(_return_ux_process(), None)
while not process:
process = next(_return_ux_process(), None)
time.sleep(0.5)

process_iter = _return_ux_process()
if len(process_iter) > 1:
print("检测到您运行了多个客户端。请选择您需要操作的客户端进程:(默认选择最近的进程)\nDetected multiple clients running. Please select a client process: (The latest process by default)")
process_dict = {"No.": ["序号"], "pid": ["进程序号"], "filePath": ["进程文件路径"], "createTime": ["进程创建时间"], "status": ["状态"]}
#process_header_df = pd.DataFrame(process_dict)
for i in range(len(process_iter)):
process_dict["No."].append(i + 1)
process_dict["pid"].append(process_iter[i].pid)
try:
process_dict["filePath"].append(process_iter[i].cmdline()[0])
except psutil.AccessDenied: #有时进程处于“已挂起”状态时,会无法访问Process类的cmdline、cwd、environ等方法(Sometimes when a process is suspended, attributes of a Process object, like `cmdline`, `cwd`, `environ`, etc., can't be accessed)
process_dict["filePath"].append(process_iter[i].exe())
process_dict["createTime"].append(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(process_iter[i].create_time())))
process_dict["status"].append(process_iter[i].status())
process_df = pd.DataFrame(process_dict)
print(format_df(process_df)[0])
running_process_df = process_df[process_df.status == "running"].sort_values(by = ["createTime"], ascending = False)
while True:
processIndex = input()
if processIndex == "":
if len(running_process_df) == 0:
print("无活动英雄联盟客户端进程。程序即将退出!\nNo active LeagueClientUx process! The program will exit now!")
time.sleep(2)
exit(1)
else:
print("已选择最近创建的英雄联盟客户端进程。\nSelected the recently created LeagueClientUx process.")
processIndex = running_process_df.iat[0, 0]
print(format_df(process_df.iloc[[0, processIndex]])[0], end = "\n\n")
try:
processIndex = int(processIndex)
except ValueError:
print("请输入不超过%d的正整数!\nPlease input an integer not greater than %d!" %(len(process_iter), len(process_iter)))
else:
if processIndex in range(1, len(process_iter) + 1):
process = process_iter[processIndex - 1]
break
else:
print("请输入不超过%d的正整数!\nPlease input an integer not greater than %d!" %(len(process_iter), len(process_iter)))
elif len(process_iter) == 1: #如果没有后面两个部分,那么在经过100次寻找进程后,由于process_iter中已经包含了所有符合要求的进程,process将成为None,从而导致self.loop.run_until_complete(connection.init())出现self中无_auth_keys的报错(If the following parts don't exist, then after 100 times of searching for the demanding process, since `process_iter` has included all the corresponding processes, `process` will become `None`, which causes an AttributeError that 'Connection' object has no attribute '_auth_key')
process = process_iter[0]
else:
raise NoLeagueClientDetected("The program didn't detect a running League Client.")
connection = Connection(self, process)
self.register_connection(connection)
self.loop.run_until_complete(connection.init())
Expand Down
4 changes: 4 additions & 0 deletions lcu_driver/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ class InvalidURI(BaseException):
def __init__(self, error_type, used_uri=None):
if error_type == 'backslash':
super().__init__(f'every endpoint must start with a backslash, replace {used_uri} by /{used_uri}')


class NoLeagueClientDetected(BaseException):
pass
9 changes: 4 additions & 5 deletions lcu_driver/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Dict, Generator

from psutil import process_iter, Process, STATUS_ZOMBIE
from psutil import process_iter, Process


def parse_cmdline_args(cmdline_args) -> Dict[str, str]:
Expand All @@ -13,9 +13,8 @@ def parse_cmdline_args(cmdline_args) -> Dict[str, str]:


def _return_ux_process() -> Generator[Process, None, None]:
processList = []
for process in process_iter():
if process.status() == STATUS_ZOMBIE:
continue

if process.name() in ['LeagueClientUx.exe', 'LeagueClientUx']:
yield process
processList.append(process)
return processList