Skip to content

Commit b934ca6

Browse files
committed
基于Python3.10全新实现的后端支持
1. 基于Python3.10和Tornado6.1 2. 异步async实现
1 parent 70b70a3 commit b934ca6

20 files changed

+1048
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ pnpm-debug.log*
2121
*.njsproj
2222
*.sln
2323
*.sw?
24+
*.pyc
25+
__pycache__

server/command/__init__.py

Whitespace-only changes.

server/command/command.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python3
2+
# Software License Agreement (BSD License)
3+
#
4+
# Copyright (c) 2022, Vinman, Inc.
5+
# All rights reserved.
6+
#
7+
# Author: Vinman <vinman.cub@gmail.com>
8+
9+
from .ide_cmd import IdeCmd
10+
11+
12+
class Command(IdeCmd):
13+
def __init__(self) -> None:
14+
super(Command, self).__init__()

server/command/ide_cmd.py

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
#!/usr/bin/env python3
2+
# Software License Agreement (BSD License)
3+
#
4+
# Copyright (c) 2022, Vinman, Inc.
5+
# All rights reserved.
6+
#
7+
# Author: Vinman <vinman.cub@gmail.com>
8+
9+
import os
10+
import jedi
11+
import time
12+
import asyncio
13+
import threading
14+
import subprocess
15+
from tornado.ioloop import IOLoop
16+
from jedi import __version__ as jedi_version
17+
from distutils.version import StrictVersion
18+
from .utils import convert_path
19+
from .resource import *
20+
from .response import response
21+
from common.config import Config
22+
23+
PROJECT_IS_EXIST = -1
24+
PROJECT_IS_NOT_EXIST = -2
25+
26+
DIR_IS_EXIST = -11
27+
DIR_IS_NOT_EXIST = -12
28+
29+
FILE_IS_EXIST = -21
30+
FILE_IS_NOT_EXIST = -22
31+
32+
jedi_is_gt_17 = StrictVersion(jedi_version) >= StrictVersion('0.17.0')
33+
34+
if not os.path.exists(os.path.join(Config.PROJECTS, 'ide')):
35+
os.makedirs(os.path.join(Config.PROJECTS, 'ide'))
36+
37+
38+
class IdeCmd(object):
39+
def __init__(self):
40+
pass
41+
42+
async def ide_list_projects(self, client, cmd_id, data):
43+
ide_path = os.path.join(Config.PROJECTS, 'ide')
44+
code, projects = list_projects(ide_path)
45+
await response(client, cmd_id, code, projects)
46+
47+
async def ide_get_project(self, client, cmd_id, data):
48+
prj_name = data.get('projectName')
49+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
50+
code, project = get_project(prj_path)
51+
await response(client, cmd_id, code, project)
52+
53+
async def ide_create_project(self, client, cmd_id, data):
54+
prj_name = data.get('projectName')
55+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
56+
code, _ = create_project(prj_path, config_data={
57+
'type': 'python',
58+
'expendKeys': ['/'],
59+
'openList': ['/main.py'],
60+
'selectFilePath': '/main.py'
61+
})
62+
if code == 0:
63+
write(os.path.join(prj_path, 'main.py'), '#!/usr/bin/env python3\n\n')
64+
await response(client, cmd_id, code, _)
65+
66+
async def ide_delete_project(self, client, cmd_id, data):
67+
prj_name = data.get('projectName')
68+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
69+
code, _ = delete(prj_path)
70+
await response(client, cmd_id, code, _)
71+
72+
async def ide_rename_project(self, client, cmd_id, data):
73+
old_name = data.get('oldName')
74+
old_path = os.path.join(Config.PROJECTS, 'ide', old_name)
75+
new_name = data.get('newName')
76+
new_path = os.path.join(Config.PROJECTS, 'ide', new_name)
77+
code, _ = rename(old_path, new_path)
78+
await response(client, cmd_id, code, _)
79+
80+
async def ide_save_project(self, client, cmd_id, data):
81+
prj_name = data.get('projectName')
82+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
83+
code, _ = save_project(prj_path, data)
84+
await response(client, cmd_id, code, _)
85+
86+
async def ide_create_file(self, client, cmd_id, data):
87+
prj_name = data.get('projectName')
88+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
89+
parent_path = convert_path(data.get('parentPath'))
90+
file_name = data.get('fileName')
91+
file_path = os.path.join(prj_path, parent_path, file_name)
92+
code, _ = write_project_file(prj_path, file_path, '')
93+
await response(client, cmd_id, code, _)
94+
95+
async def ide_write_file(self, client, cmd_id, data):
96+
prj_name = data.get('projectName')
97+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
98+
file_path = os.path.join(prj_path, convert_path(data.get('filePath')))
99+
file_data = data.get('fileData')
100+
code, _ = write_project_file(prj_path, file_path, file_data)
101+
if data.get('complete', False):
102+
line = data.get('line', None)
103+
column = data.get('column', None)
104+
line = line + 1 if line is not None else line
105+
completions = set()
106+
if jedi_is_gt_17:
107+
script = jedi.api.Script(code=file_data, path=file_path, project=jedi.api.Project(file_path, added_sys_path=[]))
108+
for completion in script.complete(line=line, column=column):
109+
completions.add(completion.name)
110+
else:
111+
script = jedi.api.Script(source=file_data, line=line, column=column, path=file_path)
112+
completions = set()
113+
for completion in script.completions():
114+
completions.add(completion.name)
115+
await response(client, cmd_id, 0, list(completions))
116+
else:
117+
await response(client, cmd_id, code, _)
118+
119+
async def ide_get_file(self, client, cmd_id, data):
120+
prj_name = data.get('projectName')
121+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
122+
file_path = os.path.join(prj_path, convert_path(data.get('filePath')))
123+
code, file_data = get_project_file(prj_path, file_path)
124+
await response(client, cmd_id, code, file_data)
125+
126+
async def ide_delete_file(self, client, cmd_id, data):
127+
prj_name = data.get('projectName')
128+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
129+
file_path = os.path.join(prj_path, convert_path(data.get('filePath')))
130+
code, _ = delete_project_file(prj_path, file_path)
131+
await response(client, cmd_id, code, _)
132+
133+
async def ide_rename_file(self, client, cmd_id, data):
134+
prj_name = data.get('projectName')
135+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
136+
old_path = os.path.join(prj_path, convert_path(data.get('oldPath')))
137+
new_name = data.get('newName')
138+
new_path = os.path.join(os.path.dirname(old_path), new_name)
139+
code, _ = rename_project_file(prj_path, old_path, new_path)
140+
await response(client, cmd_id, code, _)
141+
142+
async def ide_create_folder(self, client, cmd_id, data):
143+
prj_name = data.get('projectName')
144+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
145+
parent_path = convert_path(data.get('parentPath'))
146+
folder_name = data.get('folderName')
147+
folder_path = os.path.join(prj_path, parent_path, folder_name)
148+
code, _ = create_project_folder(prj_path, folder_path)
149+
await response(client, cmd_id, code, _)
150+
151+
async def ide_delete_folder(self, client, cmd_id, data):
152+
prj_name = data.get('projectName')
153+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
154+
folder_path = os.path.join(prj_path, convert_path(data.get('folderPath')))
155+
code, _ = delete_project_file(prj_path, folder_path)
156+
await response(client, cmd_id, code, _)
157+
158+
async def ide_rename_folder(self, client, cmd_id, data):
159+
prj_name = data.get('projectName')
160+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
161+
old_path = os.path.join(prj_path, convert_path(data.get('oldPath')))
162+
new_name = data.get('newName')
163+
new_path = os.path.join(os.path.dirname(old_path), new_name)
164+
code, _ = rename_project_file(prj_path, old_path, new_path)
165+
await response(client, cmd_id, code, _)
166+
167+
async def autocomplete_python(self, client, cmd_id, data):
168+
source = data.get('source')
169+
line = data.get('line', None)
170+
column = data.get('column', None)
171+
line = line + 1 if line is not None else line
172+
script = jedi.api.Script(source=source, line=line, column=column)
173+
completions = set()
174+
for completion in script.completions():
175+
completions.add(completion.name)
176+
await response(client, cmd_id, 0, list(completions))
177+
178+
async def run_pip_command(self, client, cmd_id, data):
179+
command = data.get('command')
180+
if not isinstance(command, str) or not command:
181+
return await response(client, cmd_id, 1111, {'stdout': 'pip command: {} error'.format(command)})
182+
else:
183+
options = data.get('options', [])
184+
if not command.startswith('pip'):
185+
List = command.split(' ')
186+
if len(List) == 1:
187+
cmd = [Config.PYTHON, '-u', '-m', 'pip', List[0], ' '.join(options)]
188+
elif len(List) > 1:
189+
cmd = [Config.PYTHON, '-u', '-m', 'pip', List[0]]
190+
for op in List[1:]:
191+
cmd.append(op)
192+
if List[1] == 'uninstall' and '-y' not in cmd:
193+
cmd.append('-y')
194+
# cmd = [Config.PYTHON, '-u', '-m', 'pip', List[0], '{} {}'.format(' '.join(List[1:]), ' '.join(options))]
195+
else:
196+
return await response(client, cmd_id, 1111, {'stdout': 'cmd error'})
197+
else:
198+
List = command.split(' ')
199+
if len(List) == 2:
200+
cmd = [Config.PYTHON, '-u', '-m', List[0], List[1], ' '.join(options)]
201+
elif len(List) > 2:
202+
cmd = [Config.PYTHON, '-u', '-m', List[0], List[1]]
203+
for op in List[2:]:
204+
cmd.append(op)
205+
if List[1] == 'uninstall' and '-y' not in cmd:
206+
cmd.append('-y')
207+
# cmd = [Config.PYTHON, '-u', '-m', List[0], List[1], '{} {}'.format(' '.join(List[2:]), ' '.join(options))]
208+
else:
209+
return await response(client, cmd_id, 1111, {'stdout': 'cmd error'})
210+
client.handler_info.set_subprogram(cmd_id, SubProgramThread(cmd, cmd_id, client, asyncio.get_event_loop()))
211+
await response(client, cmd_id, 0, None)
212+
client.handler_info.start_subprogram(cmd_id)
213+
214+
async def run_python_program(self, client, cmd_id, data):
215+
# Config.PYTHON
216+
prj_name = data.get('projectName')
217+
prj_path = os.path.join(Config.PROJECTS, 'ide', prj_name)
218+
file_path = os.path.join(prj_path, convert_path(data.get('filePath')))
219+
# print(file_path)
220+
if os.path.exists(file_path) and os.path.isfile(file_path) and file_path.endswith('.py'):
221+
cmd = [Config.PYTHON, '-u', file_path]
222+
client.handler_info.set_subprogram(cmd_id, SubProgramThread(cmd, cmd_id, client, asyncio.get_event_loop()))
223+
await response(client, cmd_id, 0, None)
224+
client.handler_info.start_subprogram(cmd_id)
225+
else:
226+
await response(client, cmd_id, 1111, {'stdout': 'File can not run'})
227+
228+
async def stop_python_program(self, client, cmd_id, data):
229+
program_id = data.get('program_id', None)
230+
client.handler_info.stop_subprogram(program_id)
231+
await response(client, cmd_id, 0, None)
232+
233+
234+
class SubProgramThread(threading.Thread):
235+
def __init__(self, cmd, cmd_id, client, event_loop):
236+
super(SubProgramThread, self).__init__()
237+
self.cmd = cmd
238+
self.cmd_id = cmd_id
239+
self.client = client
240+
self.alive = True
241+
self.daemon = True
242+
self.event_loop = event_loop
243+
self.p = None
244+
245+
def stop(self):
246+
self.alive = False
247+
if self.p:
248+
try:
249+
self.p.kill()
250+
except:
251+
pass
252+
self.p = None
253+
254+
def response_to_client(self, code, stdout):
255+
if stdout:
256+
IOLoop.current().spawn_callback(response, self.client, self.cmd_id, code, {'stdout': stdout})
257+
258+
def run_python_program(self):
259+
start_time = time.time()
260+
p = None
261+
asyncio.set_event_loop(self.event_loop)
262+
print('[{}-Program {} is start]'.format(self.client.id, self.cmd_id))
263+
try:
264+
p = subprocess.Popen(self.cmd, shell=False, universal_newlines=True,
265+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
266+
self.p = p
267+
while self.alive and p.poll() is None:
268+
if not self.client.connected:
269+
self.alive = False
270+
p.kill()
271+
self.client.handler_info.remove_subprogram(self.cmd_id)
272+
print('[{}-Program {} is kill][client is disconnect]'.format(self.client.id, self.cmd_id))
273+
return
274+
stdout = p.stdout.readline()
275+
stdout = stdout.strip()
276+
self.response_to_client(0, stdout)
277+
time.sleep(0.002)
278+
if not self.alive:
279+
self.response_to_client(1111, '[program is terminate]')
280+
p.kill()
281+
self.client.handler_info.remove_subprogram(self.cmd_id)
282+
print('[{}-Program {} is terminate]'.format(self.client.id, self.cmd_id))
283+
return
284+
try:
285+
stdout = p.stdout.read()
286+
self.response_to_client(0, stdout)
287+
except:
288+
pass
289+
if self.client.connected:
290+
stdout = '[Program exit with code {code}]'.format(code=p.returncode)
291+
else:
292+
stdout = '[Finish in {second:.2f}s with exit code {code}]'.format(second=time.time() - start_time, code=p.returncode)
293+
self.response_to_client(1111, stdout)
294+
self.client.handler_info.remove_subprogram(self.cmd_id)
295+
if p.returncode == 0:
296+
print('{}-Program {} success'.format(self.client.id, self.cmd_id))
297+
p.kill()
298+
return 'ok'
299+
else:
300+
print('{}-Program {} failed'.format(self.client.id, self.cmd_id))
301+
p.kill()
302+
return 'failed'
303+
except Exception as e:
304+
print('[{}-Program {} is exception], {}'.format(self.client.id, self.cmd_id, e))
305+
self.response_to_client(1111, '[Program is exception], {}'.format(e))
306+
finally:
307+
try:
308+
p.kill()
309+
except:
310+
pass
311+
try:
312+
self.client.handler_info.remove_subprogram(self.cmd_id)
313+
except:
314+
pass
315+
316+
def run(self):
317+
self.alive = True
318+
try:
319+
self.run_python_program()
320+
except Exception as e:
321+
print(e)
322+
pass
323+
self.alive = False
324+
325+

0 commit comments

Comments
 (0)