优化代码
This commit is contained in:
parent
10c41537c9
commit
c35c8c39d3
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
26
config.ini
26
config.ini
@ -8,17 +8,27 @@ now_ip = 192.168.0.186
|
|||||||
new_ip = 192.168.0.186
|
new_ip = 192.168.0.186
|
||||||
|
|
||||||
[SERVER_PATH]
|
[SERVER_PATH]
|
||||||
php_path = ${dir}phpstudy_pro\Extensions\php\php5.6.9nts\
|
php_path = ${dir}tool\php\php5.6.9nts\
|
||||||
php_spawner_path = ${dir}phpstudy_pro\COM\
|
php_spawner_path = ${dir}tool\php-cgi-spawner\
|
||||||
php_cgi_port = 9001
|
php_cgi_port = 9001
|
||||||
nginx_path = ${dir}phpstudy_pro\Extensions\Nginx1.15.11\
|
nginx_path = ${dir}tool\Nginx1.15.11\
|
||||||
mysql_path = ${dir}phpstudy_pro\Extensions\MySQL5.7.26\
|
mysql_path = ${dir}tool\MySQL5.7.26\
|
||||||
|
game_path = ${dir}server\Common\AMServer\x64\AMServer64_R.exe|${dir}server\Common\LocalLogServer\x64\LocalLogServer64_R.exe|${dir}server\Common\LoggerServer\x64\LoggerServer64_R.exe|${dir}server\Common\SessionServer\x64\SessionServer64_R.exe|${dir}server\Common\NameServer\x64\NameServer64_R.exe|${dir}server\Common\BackStageServer\x64\BackStageServer64_R.exe,10|${dir}server\Server\DBServer\x64\DBServer64_R.exe|${dir}server\Server\GateServer\x64\GateServer64_R.exe|${dir}server\Server\LogicServer\x64\LogicServerCQ64_R.exe|${dir}server\Server2\DBServer\x64\DBServer64_R.exe|${dir}server\Server2\GateServer\x64\GateServer64_R.exe|${dir}server\Server2\LogicServer\x64\LogicServerCQ64_R.exe|${dir}server\Server跨服\DBServer\x64\DBServer64_R.exe|${dir}server\Server跨服\GateServer\x64\GateServer64_R.exe|${dir}server\Server跨服\LogicServer\x64\LogicServerCQ64_R.exe
|
||||||
|
mod_file_path = ${dir}测试1.txt|${dir}测试2.txt|${CLIENT_PATH:android_path}assets\data\scripts\platform\platform_adapter.lua|${CLIENT_PATH:android_path}assets\data\scripts\platform\windows\platform_adapter.lua|${dir}server\server\LogicServer\crossserver.txt|${dir}server\server\DBServer\crossserver.txt|${dir}server\server2\LogicServer\crossserver.txt|${dir}server\server2\DBServer\crossserver.txt|${dir}server\server跨服\LogicServer\crossserver.txt|${dir}server\server跨服\DBServer\crossserver.txt|${dir}web\args.php|${dir}web\args_ios.php|${dir}web\api\notice.php
|
||||||
|
|
||||||
[GAME_PATH]
|
[CLIENT_PATH]
|
||||||
game_path = ${dir}build_pub\Common\AMServer\x64\AMServer64_R.exe|${dir}build_pub\Common\LocalLogServer\x64\LocalLogServer64_R.exe|${dir}build_pub\Common\LoggerServer\x64\LoggerServer64_R.exe|${dir}build_pub\Common\SessionServer\x64\SessionServer64_R.exe|${dir}build_pub\Common\NameServer\x64\NameServer64_R.exe|${dir}build_pub\Common\BackStageServer\x64\BackStageServer64_R.exe,10|${dir}build_pub\Server\DBServer\x64\DBServer64_R.exe|${dir}build_pub\Server\GateServer\x64\GateServer64_R.exe|${dir}build_pub\Server\LogicServer\x64\LogicServerCQ64_R.exe|${dir}build_pub\Server2\DBServer\x64\DBServer64_R.exe|${dir}build_pub\Server2\GateServer\x64\GateServer64_R.exe|${dir}build_pub\Server2\LogicServer\x64\LogicServerCQ64_R.exe|${dir}build_pub\Server跨服\DBServer\x64\DBServer64_R.exe|${dir}build_pub\Server跨服\GateServer\x64\GateServer64_R.exe|${dir}build_pub\Server跨服\LogicServer\x64\LogicServerCQ64_R.exe
|
client_path = ${dir}client\
|
||||||
|
android_name = game.apk
|
||||||
|
android_path = ${client_path}game\
|
||||||
|
tmp_android_name = game_tmp.apk
|
||||||
|
new_android_name = game_signed.apk
|
||||||
|
pc_path = ${client_path}pc\
|
||||||
|
|
||||||
[MODFILE]
|
[TOOL_PATH]
|
||||||
dirname = ${dir}测试1.txt|${dir}测试2.txt|${dir}apk\game\assets\data\scripts\platform\platform_adapter.lua|${dir}apk\game\assets\data\scripts\platform\windows\platform_adapter.lua|${dir}build_pub\server\LogicServer\crossserver.txt|${dir}build_pub\server\DBServer\crossserver.txt|${dir}build_pub\server2\LogicServer\crossserver.txt|${dir}build_pub\server2\DBServer\crossserver.txt|${dir}build_pub\server跨服\LogicServer\crossserver.txt|${dir}build_pub\server跨服\DBServer\crossserver.txt|${dir}phpstudy_pro\WWW\args.php|${dir}phpstudy_pro\WWW\args_ios.php|${dir}phpstudy_pro\WWW\api\notice.php
|
apktool_path = ${dir}tool\apktool\
|
||||||
|
heidisql_path = ${dir}tool\HeidiSQL\heidisql.exe
|
||||||
|
notepad3_path = ${dir}tool\Notepad3\Notepad3.exe
|
||||||
|
composer_path = ${dir}tool\Composer\
|
||||||
|
|
||||||
[OTHER]
|
[OTHER]
|
||||||
about = shileiye
|
about = shileiye
|
||||||
|
302
dome.py
302
dome.py
@ -1,275 +1,55 @@
|
|||||||
# -*- coding:utf-8 -*-
|
|
||||||
from pyec.wx模块 import *
|
|
||||||
import wx
|
|
||||||
import configparser
|
|
||||||
import re
|
|
||||||
import psutil
|
|
||||||
import time
|
|
||||||
from subprocess import Popen
|
|
||||||
import sys
|
import sys
|
||||||
import mainui
|
import time
|
||||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
|
import socket
|
||||||
|
|
||||||
# 界面文件为 FirstMainWin.py
|
|
||||||
from mainui import *
|
|
||||||
import requests
|
|
||||||
import os
|
import os
|
||||||
|
from message import Ui_Form
|
||||||
configFile = 'config.ini'
|
from PyQt5.QtGui import *
|
||||||
# 创建配置文件对象
|
from PyQt5.QtWidgets import *
|
||||||
config = configparser.ConfigParser()
|
from PyQt5.QtCore import *
|
||||||
# 读取文件
|
|
||||||
config.read(configFile, encoding='utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
# 主类
|
||||||
|
class mymainwindow(QMainWindow, Ui_Form):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(MainWindow, self).__init__(parent)
|
super(mymainwindow, self).__init__(parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
# 初始赋值
|
|
||||||
self.nowIpLabel.setText(config['DEFAULT']['now_ip'])
|
|
||||||
self.newIpInput.setText(config['DEFAULT']['new_ip'])
|
|
||||||
self.nowGameLabel.setText(config['DEFAULT']['game_name'])
|
|
||||||
self.newGameInput.setText(config['DEFAULT']['game_name'])
|
|
||||||
if isinstance(checkprocess("nginx.exe"), int):
|
|
||||||
# self.启动Nginx.Label = '关闭Nginx'
|
|
||||||
self.nginxButton.setText("关闭Nginx")
|
|
||||||
else:
|
|
||||||
# self.启动Nginx.Label = '启动Nginx'
|
|
||||||
self.nginxButton.setText("启动Nginx")
|
|
||||||
|
|
||||||
def saveSet(self, event):
|
# 线程测试开始
|
||||||
print('保存设置,按钮被单击')
|
def threadstartslot(self):
|
||||||
message = QMessageBox.information(self, "提示信息", "确定要保存设置吗?", QMessageBox.Ok | QMessageBox.No, QMessageBox.No);
|
self.work = Thread()
|
||||||
if message == QMessageBox.No:
|
self.work.trigger.connect(self.deal) # 线程中的trigger与主类中的方法进行绑定
|
||||||
return
|
self.work.start() # 开启线程
|
||||||
now_ip = config['DEFAULT']['now_ip']
|
|
||||||
new_ip = self.newIpInput.text()
|
|
||||||
now_game = config['DEFAULT']['game_name']
|
|
||||||
new_game = self.newGameInput.text()
|
|
||||||
compile_ip = re.compile('^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$')
|
|
||||||
# 验证IP地址格式
|
|
||||||
if (new_ip == "") or new_game == "" or not (compile_ip.match(new_ip)):
|
|
||||||
QMessageBox.information(self, "提示信息", "IP地址、游戏名称为空或有误,请正确填写IPv4地址或游戏名称!", QMessageBox.Ok, QMessageBox.Ok);
|
|
||||||
return
|
|
||||||
dirname = config['MODFILE']['dirname']
|
|
||||||
print(dirname.split("|")[1])
|
|
||||||
# filedir = dirname.split("|")
|
|
||||||
sum = 0
|
|
||||||
for filedir in dirname.split("|"):
|
|
||||||
# fileencoding = filedir.split(",")
|
|
||||||
# 执行文本替换
|
|
||||||
if len(filedir.split(",")) < 2:
|
|
||||||
isencoding = config['DEFAULT']['file_encoding']
|
|
||||||
else:
|
|
||||||
isencoding = filedir.split(",")[1]
|
|
||||||
filename = config['DEFAULT']['dir'] + filedir.split(",")[0]
|
|
||||||
|
|
||||||
try:
|
# 线程测试停止
|
||||||
with open(filename, 'rt+', encoding=isencoding) as f:
|
def threadstopslot(self):
|
||||||
t = f.read()
|
self.work.threadstartflag = False
|
||||||
t = t.replace(now_ip, new_ip)
|
|
||||||
t = t.replace(now_game, new_game)
|
|
||||||
# 读写偏移位置移到最开始处
|
|
||||||
f.seek(0, 0)
|
|
||||||
f.write(t)
|
|
||||||
# 设置文件结尾 EOF
|
|
||||||
f.truncate()
|
|
||||||
sum = sum + 1
|
|
||||||
except:
|
|
||||||
QMessageBox.information(self, "提示信息", "替换“" + filename + "”出错,可能文件不存在或编码不对,请检查需要替换的文件编码与配置文件编码是否相符!", QMessageBox.Ok, QMessageBox.Ok)
|
|
||||||
return
|
|
||||||
# 写入配置文件
|
|
||||||
config.set('DEFAULT', 'game_name', new_game)
|
|
||||||
config.set('DEFAULT', 'now_ip', new_ip)
|
|
||||||
config.set('DEFAULT', 'new_ip', new_ip)
|
|
||||||
saveconfig = open(configFile, 'wt', encoding=config['DEFAULT']['file_encoding'])
|
|
||||||
config.write(saveconfig) # 把要修改的节点的内容写到文件中
|
|
||||||
saveconfig.close()
|
|
||||||
# 设置界面显示
|
|
||||||
self.nowGameLabel.setText(new_game)
|
|
||||||
self.nowIpLabel.setText(new_ip)
|
|
||||||
# 成功提示
|
|
||||||
QMessageBox.information(self, "提示信息", "游戏名称和IP修改成功!", QMessageBox.Ok, QMessageBox.Ok)
|
|
||||||
print('修改完成:' + new_game + ':' + new_ip)
|
|
||||||
return
|
|
||||||
|
|
||||||
def nginx(self, event):
|
# 更新UI方法
|
||||||
if self.nginxButton.text() == '启动Nginx':
|
def deal(self, str):
|
||||||
nginx_process = Popen('start .\\nginx.exe',
|
self.textEdit.append(str)
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'phpstudy_pro\\Extensions\\Nginx1.15.11',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
php_process = Popen('.\\COM\\php-cgi-spawner.exe "Extensions\\php\\php5.6.9nts\\php-cgi.exe -c Extensions\\php\\php5.6.9nts\\php.ini" 9001 1+16',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'phpstudy_pro',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
self.nginxButton.setText("关闭Nginx")
|
|
||||||
else:
|
|
||||||
nginx_process = Popen('.\\nginx.exe -s stop',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'phpstudy_pro\\Extensions\\Nginx1.15.11',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
PHP_process = Popen('taskkill /f /t /im php-cgi.exe',
|
|
||||||
shell=True,
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
spawner_process = Popen('taskkill /f /t /im php-cgi-spawner.exe',
|
|
||||||
shell=True,
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
self.nginxButton.setText("启动Nginx")
|
|
||||||
# print(process)
|
|
||||||
# os.chdir("D:\\LYserver\\phpstudy_pro\\Extensions\\Nginx1.15.11")
|
|
||||||
# nginxtype = os.system('start .\\nginx.exe') # 不弹出界面
|
|
||||||
# if nginxtype
|
|
||||||
# os.system('start .\\nginx.exe') #弹出界面
|
|
||||||
print('启动Nginx,按钮被单击')
|
|
||||||
|
|
||||||
def mysql(self, event):
|
|
||||||
if self.启动MySql.Label == '启动MySql':
|
|
||||||
php_process = Popen('.\\mysqld.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'phpstudy_pro\\Extensions\\MySQL5.7.26\\bin',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
self.启动MySql.Label = '关闭MySql'
|
|
||||||
else:
|
|
||||||
kill_process = Popen('taskkill /f /t /im mysqld.exe',
|
|
||||||
shell=True,
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
self.启动MySql.Label = '启动MySql'
|
|
||||||
print('启动MySql,按钮被单击')
|
|
||||||
|
|
||||||
def game(self, event):
|
|
||||||
if self.启动服务端.Label == '启动服务端':
|
|
||||||
AMServer64_R = Popen('start .\\AMServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Common\\AMServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
LocalLogServer64_R = Popen('start .\\LocalLogServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Common\\LocalLogServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
LoggerServer64_R = Popen('start .\\LoggerServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Common\\LoggerServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
NameServer64_R = Popen('start .\\SessionServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Common\\SessionServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
NameServer64_R = Popen('start .\\NameServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Common\\NameServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
BackStageServer64_R = Popen('start .\\BackStageServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Common\\BackStageServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
time.sleep(5) # 延迟5秒
|
|
||||||
# 一区
|
|
||||||
DBServer64_R = Popen('start .\\DBServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server\\DBServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
GateServer64_R = Popen('start .\\GateServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server\\GateServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
LogicServerCQ64_R = Popen('start .\\LogicServerCQ64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server\\LogicServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
# 二区
|
|
||||||
DBServer64_R = Popen('start .\\DBServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server2\\DBServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
GateServer64_R = Popen('start .\\GateServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server2\\GateServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
LogicServerCQ64_R = Popen('start .\\LogicServerCQ64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server2\\LogicServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
# 跨服
|
|
||||||
DBServer64_R = Popen('start .\\DBServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server跨服\\DBServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
GateServer64_R = Popen('start .\\GateServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server跨服\\GateServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
LogicServerCQ64_R = Popen('start .\\LogicServerCQ64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'build_pub\\Server跨服\\LogicServer\\x64',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
self.启动服务端.Label = '关闭服务端'
|
|
||||||
else:
|
|
||||||
kill_process = Popen(
|
|
||||||
'taskkill /f /t /im LocalLogServer64_R.exe & taskkill /f /t /im LoggerServer64_R.exe & taskkill /f /t /im SessionServer64_R.exe & taskkill /f /t /im NameServer64_R.exe & taskkill /f /t /im LogicServerCQ64_R.exe & taskkill /f /t /im GateServer64_R.exe & taskkill /f /t /im DBCenterServer64_R.exe & taskkill /f /t /im DBServer64_R.exe & taskkill /f /t /im BackStageServer64_R.exe & taskkill /f /t /im AMServer64_R.exe',
|
|
||||||
shell=True,
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
self.启动服务端.Label = '启动服务端'
|
|
||||||
print('启动服务端,按钮被单击')
|
|
||||||
|
|
||||||
def fanbianyi(self, event):
|
|
||||||
apktool = Popen('apktool d game.apk',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'apk',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
print('反编译APK,按钮被单击')
|
|
||||||
|
|
||||||
def shengcheng(self, event):
|
|
||||||
apktool = Popen('apktool b game',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'apk',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
print('生成APK,按钮被单击')
|
|
||||||
|
|
||||||
def qianming(self, event):
|
|
||||||
apktool = Popen('jarsigner -verbose -keystore key.keystore -storepass 123456 -signedjar game-signed.apk game/dist/game.apk key.keystore',
|
|
||||||
shell=True,
|
|
||||||
cwd=config['DEFAULT']['dir'] + 'apk',
|
|
||||||
# encoding='utf-8'
|
|
||||||
)
|
|
||||||
print('APK签名,按钮被单击')
|
|
||||||
|
|
||||||
# 进程判断,返回进程ID
|
|
||||||
def checkprocess(processname):
|
|
||||||
pl = psutil.pids()
|
|
||||||
for pid in pl:
|
|
||||||
if psutil.Process(pid).name() == processname:
|
|
||||||
return pid
|
|
||||||
|
|
||||||
|
|
||||||
|
# 线程类
|
||||||
|
class Thread(QThread):
|
||||||
|
trigger = pyqtSignal(str) # 注意pyqtSignal一定要实例到__init__前面
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Thread, self).__init__()
|
||||||
|
# 定义的变量
|
||||||
|
self.threadstartflag = True
|
||||||
|
self.timecount = 0
|
||||||
|
|
||||||
|
# 执行耗时操作
|
||||||
|
def run(self):
|
||||||
|
while self.threadstartflag == True:
|
||||||
|
self.trigger.emit(u"计时%d" % self.timecount) # 发送更新GUI的信号
|
||||||
|
self.timecount += 1
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
# 显示GUI
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
myWin = MainWindow()
|
MainWindow = mymainwindow()
|
||||||
myWin.show()
|
MainWindow.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
130
main.py
130
main.py
@ -1,11 +1,17 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import ast
|
import ast
|
||||||
import configparser
|
import configparser
|
||||||
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
import time
|
import time
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, QThread
|
||||||
|
from PyQt5.QtGui import QTextCursor
|
||||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
|
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
|
||||||
from configparser import ExtendedInterpolation
|
from configparser import ExtendedInterpolation
|
||||||
|
|
||||||
@ -25,16 +31,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(MainWindow, self).__init__(parent)
|
super(MainWindow, self).__init__(parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
|
self.apktool_path = config['TOOL_PATH']['apktool_path']
|
||||||
|
self.android_name = config['CLIENT_PATH']['client_path'] + config['CLIENT_PATH']['android_name']
|
||||||
|
self.android_path = config['CLIENT_PATH']['android_path']
|
||||||
|
self.tmp_android_name = config['CLIENT_PATH']['client_path'] + config['CLIENT_PATH']['tmp_android_name']
|
||||||
|
self.new_android_name = config['CLIENT_PATH']['client_path'] + config['CLIENT_PATH']['new_android_name']
|
||||||
|
|
||||||
# 初始赋值
|
# 初始赋值
|
||||||
self.nowIpLabel.setText(config['GAME_SET']['now_ip'])
|
self.nowIpLabel.setText(config['GAME_SET']['now_ip'])
|
||||||
self.newIpInput.setText(config['GAME_SET']['new_ip'])
|
self.newIpInput.setText(config['GAME_SET']['new_ip'])
|
||||||
self.nowGameLabel.setText(config['GAME_SET']['game_name'])
|
self.nowGameLabel.setText(config['GAME_SET']['game_name'])
|
||||||
self.newGameInput.setText(config['GAME_SET']['game_name'])
|
self.newGameInput.setText(config['GAME_SET']['game_name'])
|
||||||
if isinstance(checkprocess("nginx.exe"), int):
|
if isinstance(checkprocess("nginx.exe"), int):
|
||||||
# self.启动Nginx.Label = '关闭Nginx'
|
|
||||||
self.nginxButton.setText("关闭Nginx")
|
self.nginxButton.setText("关闭Nginx")
|
||||||
else:
|
else:
|
||||||
# self.启动Nginx.Label = '启动Nginx'
|
|
||||||
self.nginxButton.setText("启动Nginx")
|
self.nginxButton.setText("启动Nginx")
|
||||||
|
|
||||||
def saveSet(self, event):
|
def saveSet(self, event):
|
||||||
@ -61,7 +72,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
if (new_ip == "") or new_game == "" or not (compile_ip.match(new_ip)):
|
if (new_ip == "") or new_game == "" or not (compile_ip.match(new_ip)):
|
||||||
QMessageBox.information(self, "提示信息", "IP地址、游戏名称为空或有误,请正确填写IPv4地址或游戏名称!")
|
QMessageBox.information(self, "提示信息", "IP地址、游戏名称为空或有误,请正确填写IPv4地址或游戏名称!")
|
||||||
return
|
return
|
||||||
dirname = config['MODFILE']['dirname']
|
dirname = config['SERVER_PATH']['mod_file_path']
|
||||||
sum = 0
|
sum = 0
|
||||||
for filedir in dirname.split("|"):
|
for filedir in dirname.split("|"):
|
||||||
# 执行文本替换
|
# 执行文本替换
|
||||||
@ -102,12 +113,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def nginx(self, event):
|
def nginx(self, event):
|
||||||
if self.nginxButton.text() == '启动Nginx':
|
if self.nginxButton.text() == '启动Nginx':
|
||||||
nginx_process = Popen('start .\\nginx.exe',
|
Popen('start .\\nginx.exe',
|
||||||
shell=True,
|
shell=True,
|
||||||
cwd=config['SERVER_PATH']['nginx_path'],
|
cwd=config['SERVER_PATH']['nginx_path'],
|
||||||
# encoding='utf-8'
|
# encoding='utf-8'
|
||||||
)
|
)
|
||||||
php_process = Popen(config['SERVER_PATH']['php_spawner_path'] + 'php-cgi-spawner.exe "' + config['SERVER_PATH']['php_path'] + 'php-cgi.exe -c ' + config['SERVER_PATH']['php_path'] + 'php.ini" ' + config['SERVER_PATH']['php_cgi_port'] + ' 1+16',
|
Popen(
|
||||||
|
config['SERVER_PATH']['php_spawner_path'] + 'php-cgi-spawner.exe "' + config['SERVER_PATH']['php_path'] + 'php-cgi.exe -c ' + config['SERVER_PATH']['php_path'] + 'php.ini" ' + config['SERVER_PATH']['php_cgi_port'] + ' 1+16',
|
||||||
shell=True,
|
shell=True,
|
||||||
# cwd=config['DEFAULT']['dir'] + 'phpstudy_pro',
|
# cwd=config['DEFAULT']['dir'] + 'phpstudy_pro',
|
||||||
# encoding='utf-8'
|
# encoding='utf-8'
|
||||||
@ -137,14 +149,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def mysql(self, event):
|
def mysql(self, event):
|
||||||
if self.mysqlButton.text() == '启动MySql':
|
if self.mysqlButton.text() == '启动MySql':
|
||||||
php_process = Popen('.\\mysqld.exe',
|
Popen('.\\mysqld.exe',
|
||||||
shell=True,
|
shell=True,
|
||||||
cwd=config['SERVER_PATH']['mysql_path'] + 'bin',
|
cwd=config['SERVER_PATH']['mysql_path'] + 'bin',
|
||||||
# encoding='utf-8'
|
# encoding='utf-8'
|
||||||
)
|
)
|
||||||
self.mysqlButton.setText("关闭MySql")
|
self.mysqlButton.setText("关闭MySql")
|
||||||
else:
|
else:
|
||||||
kill_process = Popen('taskkill /f /t /im mysqld.exe',
|
Popen('taskkill /f /t /im mysqld.exe',
|
||||||
shell=True,
|
shell=True,
|
||||||
# encoding='utf-8'
|
# encoding='utf-8'
|
||||||
)
|
)
|
||||||
@ -153,9 +165,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def game(self, event):
|
def game(self, event):
|
||||||
if self.gameButton.text() == '启动游戏':
|
if self.gameButton.text() == '启动游戏':
|
||||||
# tinydict = ast.literal_eval(config['GAME_PATH']['game_path'])
|
game_path = config['SERVER_PATH']['game_path'].split("|")
|
||||||
game_path = config['GAME_PATH']['game_path'].split("|")
|
|
||||||
# print("字典值 : %s" %game_path.items())
|
|
||||||
# 遍历字典列表
|
# 遍历字典列表
|
||||||
for f in game_path:
|
for f in game_path:
|
||||||
print(f)
|
print(f)
|
||||||
@ -186,29 +196,91 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||||||
print('启动游戏,按钮被单击')
|
print('启动游戏,按钮被单击')
|
||||||
|
|
||||||
def fanbianyi(self, event):
|
def fanbianyi(self, event):
|
||||||
apktool = Popen('apktool d game.apk',
|
obj = self.fanbianyiButton
|
||||||
shell=True,
|
# obj.setText('运行中')
|
||||||
cwd=config['DEFAULT']['dir'] + 'apk',
|
title = obj.text()
|
||||||
# encoding='utf-8'
|
cmd = 'apktool d ' + self.android_name + ' -f -o ' + self.android_path
|
||||||
)
|
cwd = self.apktool_path
|
||||||
|
code = "gbk"
|
||||||
|
path = self.android_path
|
||||||
|
self.add_cmd_msg(cmd, cwd, title, obj, path, code)
|
||||||
print('反编译APK,按钮被单击')
|
print('反编译APK,按钮被单击')
|
||||||
|
|
||||||
def shengcheng(self, event):
|
def shengcheng(self, event):
|
||||||
apktool = Popen('apktool b game',
|
obj = self.shengchengButton
|
||||||
shell=True,
|
# obj.setText('运行中')
|
||||||
cwd=config['DEFAULT']['dir'] + 'apk',
|
title = obj.text()
|
||||||
# encoding='utf-8'
|
cmd = 'apktool b ' + self.android_path + ' -o ' + self.tmp_android_name
|
||||||
)
|
cwd = self.apktool_path
|
||||||
|
code = "gbk"
|
||||||
|
path = self.tmp_android_name
|
||||||
|
self.add_cmd_msg(cmd, cwd, title, obj, path, code)
|
||||||
print('生成APK,按钮被单击')
|
print('生成APK,按钮被单击')
|
||||||
|
|
||||||
def qianming(self, event):
|
def qianming(self, event):
|
||||||
apktool = Popen('jarsigner -verbose -keystore key.keystore -storepass 123456 -signedjar game-signed.apk game/dist/game.apk key.keystore',
|
obj = self.qianmingButton
|
||||||
shell=True,
|
# obj.setText('运行中')
|
||||||
cwd=config['DEFAULT']['dir'] + 'apk',
|
title = obj.text()
|
||||||
# encoding='utf-8'
|
cmd = 'jarsigner -verbose -keystore key.keystore -storepass 123456 -signedjar ' + self.new_android_name + ' ' + self.tmp_android_name + ' key.keystore'
|
||||||
)
|
cwd = self.apktool_path
|
||||||
|
code = "gbk"
|
||||||
|
path = self.new_android_name
|
||||||
|
self.add_cmd_msg(cmd, cwd, title, obj, path, code)
|
||||||
print('APK签名,按钮被单击')
|
print('APK签名,按钮被单击')
|
||||||
|
|
||||||
|
# 利用新线程进行脚本执行回显
|
||||||
|
def add_cmd_msg(self, cmd, cwd="", title="", obj="", path="", code="utf8"):
|
||||||
|
obj.setEnabled(False)
|
||||||
|
self.CMDtextEdit.clear()
|
||||||
|
self.CMDtextEdit.append(title + "脚本开始执行!")
|
||||||
|
self.work = Thread(cmd, cwd, title, obj, path, code)
|
||||||
|
self.work.trigger.connect(self.show_msg) # 线程中的trigger与主类中的方法进行绑定
|
||||||
|
self.work.daemon = True # 主进程退出则该线程也退出
|
||||||
|
self.work.start() # 开启线程
|
||||||
|
|
||||||
|
# 更新UI方法
|
||||||
|
def show_msg(self, str):
|
||||||
|
self.CMDtextEdit.moveCursor(QTextCursor.End)
|
||||||
|
self.CMDtextEdit.append(str)
|
||||||
|
# self.CMDtextEdit.insertPlainText(str)
|
||||||
|
# self.CMDtextEdit.insertHtml(str)
|
||||||
|
|
||||||
|
|
||||||
|
# 线程类
|
||||||
|
class Thread(QThread):
|
||||||
|
trigger = pyqtSignal(str) # 注意pyqtSignal一定要实例到__init__前面
|
||||||
|
|
||||||
|
def __init__(self, cmd, cwd="", title="", obj="", path="", code="utf8"):
|
||||||
|
super(Thread, self).__init__()
|
||||||
|
# 定义的变量
|
||||||
|
self.cmd = cmd
|
||||||
|
self.cwd = cwd
|
||||||
|
self.title = title
|
||||||
|
self.obj = obj
|
||||||
|
self.path = path
|
||||||
|
self.code = code
|
||||||
|
self.state = True
|
||||||
|
|
||||||
|
# 执行耗时操作
|
||||||
|
def run(self):
|
||||||
|
process = subprocess.Popen(self.cmd, shell=True, cwd=self.cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
while process.poll() is None:
|
||||||
|
line = process.stdout.readline()
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
self.trigger.emit(line.decode(self.code, 'ignore'))
|
||||||
|
print(line.decode(self.code, 'ignore'))
|
||||||
|
self.trigger.emit(self.title + '脚本执行结束!')
|
||||||
|
self.obj.setEnabled(True)
|
||||||
|
# self.obj.setText(self.title)
|
||||||
|
# print(self.obj)
|
||||||
|
# self.fanbianyiButton.setEnabled(False)
|
||||||
|
# self.fanbianyiButton.setText('运行中')
|
||||||
|
if self.path != "":
|
||||||
|
self.trigger.emit('文件路径:' + self.path)
|
||||||
|
startfile(self.path)
|
||||||
|
|
||||||
|
|
||||||
# 进程判断,返回进程ID
|
# 进程判断,返回进程ID
|
||||||
def checkprocess(processname):
|
def checkprocess(processname):
|
||||||
pl = psutil.pids()
|
pl = psutil.pids()
|
||||||
@ -217,6 +289,14 @@ def checkprocess(processname):
|
|||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
|
||||||
|
# 打开文件或文件夹
|
||||||
|
def startfile(filename):
|
||||||
|
try:
|
||||||
|
os.startfile(f'explorer /select, "{pathlib.Path(filename)}')
|
||||||
|
except:
|
||||||
|
subprocess.Popen(f'explorer /select, "{pathlib.Path(filename)}')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
myWin = MainWindow()
|
myWin = MainWindow()
|
||||||
|
16
mainui.py
16
mainui.py
@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||||||
class Ui_MainWindow(object):
|
class Ui_MainWindow(object):
|
||||||
def setupUi(self, MainWindow):
|
def setupUi(self, MainWindow):
|
||||||
MainWindow.setObjectName("MainWindow")
|
MainWindow.setObjectName("MainWindow")
|
||||||
MainWindow.resize(560, 333)
|
MainWindow.resize(560, 530)
|
||||||
MainWindow.setMouseTracking(False)
|
MainWindow.setMouseTracking(False)
|
||||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
||||||
self.centralwidget.setObjectName("centralwidget")
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
@ -26,6 +26,7 @@ class Ui_MainWindow(object):
|
|||||||
self.saveSetButton.setObjectName("saveSetButton")
|
self.saveSetButton.setObjectName("saveSetButton")
|
||||||
self.newGameInput = QtWidgets.QLineEdit(self.groupBox)
|
self.newGameInput = QtWidgets.QLineEdit(self.groupBox)
|
||||||
self.newGameInput.setGeometry(QtCore.QRect(70, 60, 120, 20))
|
self.newGameInput.setGeometry(QtCore.QRect(70, 60, 120, 20))
|
||||||
|
self.newGameInput.setText("")
|
||||||
self.newGameInput.setObjectName("newGameInput")
|
self.newGameInput.setObjectName("newGameInput")
|
||||||
self.label_4 = QtWidgets.QLabel(self.groupBox)
|
self.label_4 = QtWidgets.QLabel(self.groupBox)
|
||||||
self.label_4.setGeometry(QtCore.QRect(210, 60, 60, 20))
|
self.label_4.setGeometry(QtCore.QRect(210, 60, 60, 20))
|
||||||
@ -80,6 +81,11 @@ class Ui_MainWindow(object):
|
|||||||
self.qianmingButton = QtWidgets.QPushButton(self.groupBox_3)
|
self.qianmingButton = QtWidgets.QPushButton(self.groupBox_3)
|
||||||
self.qianmingButton.setGeometry(QtCore.QRect(190, 20, 80, 45))
|
self.qianmingButton.setGeometry(QtCore.QRect(190, 20, 80, 45))
|
||||||
self.qianmingButton.setObjectName("qianmingButton")
|
self.qianmingButton.setObjectName("qianmingButton")
|
||||||
|
self.CMDtextEdit = QtWidgets.QTextEdit(self.centralwidget)
|
||||||
|
self.CMDtextEdit.setGeometry(QtCore.QRect(20, 320, 521, 180))
|
||||||
|
self.CMDtextEdit.setStyleSheet("background-color: rgb(0, 0, 0);\n"
|
||||||
|
"color: rgb(40, 255, 16);")
|
||||||
|
self.CMDtextEdit.setObjectName("CMDtextEdit")
|
||||||
MainWindow.setCentralWidget(self.centralwidget)
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
self.statusbar = QtWidgets.QStatusBar(MainWindow)
|
self.statusbar = QtWidgets.QStatusBar(MainWindow)
|
||||||
self.statusbar.setObjectName("statusbar")
|
self.statusbar.setObjectName("statusbar")
|
||||||
@ -95,12 +101,13 @@ class Ui_MainWindow(object):
|
|||||||
self.fanbianyiButton.clicked.connect(MainWindow.fanbianyi)
|
self.fanbianyiButton.clicked.connect(MainWindow.fanbianyi)
|
||||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
MainWindow.setWindowTitle(_translate("MainWindow", "通用游戏部署工具 v1.0 By:shileiye"))
|
MainWindow.setWindowTitle(_translate("MainWindow", "通用游戏部署工具 v1.0 By:shileiye"))
|
||||||
self.groupBox.setTitle(_translate("MainWindow", "基本设置"))
|
self.groupBox.setTitle(_translate("MainWindow", "基本设置"))
|
||||||
self.saveSetButton.setText(_translate("MainWindow", "保存"))
|
self.saveSetButton.setText(_translate("MainWindow", "保存"))
|
||||||
self.newGameInput.setText(_translate("MainWindow", "撒打算"))
|
|
||||||
self.newGameInput.setPlaceholderText(_translate("MainWindow", "输入新名称"))
|
self.newGameInput.setPlaceholderText(_translate("MainWindow", "输入新名称"))
|
||||||
self.label_4.setText(_translate("MainWindow", "新IP:"))
|
self.label_4.setText(_translate("MainWindow", "新IP:"))
|
||||||
self.newIpInput.setPlaceholderText(_translate("MainWindow", "输入IP"))
|
self.newIpInput.setPlaceholderText(_translate("MainWindow", "输入IP"))
|
||||||
@ -117,4 +124,9 @@ class Ui_MainWindow(object):
|
|||||||
self.fanbianyiButton.setText(_translate("MainWindow", "反编译APK"))
|
self.fanbianyiButton.setText(_translate("MainWindow", "反编译APK"))
|
||||||
self.shengchengButton.setText(_translate("MainWindow", "生成APK"))
|
self.shengchengButton.setText(_translate("MainWindow", "生成APK"))
|
||||||
self.qianmingButton.setText(_translate("MainWindow", "APK签名"))
|
self.qianmingButton.setText(_translate("MainWindow", "APK签名"))
|
||||||
|
self.CMDtextEdit.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
|
||||||
|
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
|
||||||
|
"p, li { white-space: pre-wrap; }\n"
|
||||||
|
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
|
||||||
|
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">准备就绪,请在上方进行操作,部分操作进程和结果会在此处显示。</p></body></html>"))
|
||||||
import icon_rc
|
import icon_rc
|
||||||
|
25
mainui.ui
25
mainui.ui
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>560</width>
|
<width>560</width>
|
||||||
<height>333</height>
|
<height>530</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="mouseTracking">
|
<property name="mouseTracking">
|
||||||
@ -52,7 +52,7 @@
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>撒打算</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string>输入新名称</string>
|
<string>输入新名称</string>
|
||||||
@ -278,6 +278,27 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QTextEdit" name="CMDtextEdit">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>20</x>
|
||||||
|
<y>320</y>
|
||||||
|
<width>521</width>
|
||||||
|
<height>180</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: rgb(0, 0, 0);
|
||||||
|
color: rgb(40, 255, 16);</string>
|
||||||
|
</property>
|
||||||
|
<property name="html">
|
||||||
|
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
|
p, li { white-space: pre-wrap; }
|
||||||
|
</style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||||
|
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">准备就绪,请在上方进行操作,部分操作进程和结果会在此处显示。</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QStatusBar" name="statusbar"/>
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user