gomyck-tools 1.3.1__py3-none-any.whl → 1.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. ctools/__init__.py +0 -0
  2. ctools/aes_tools.py +35 -0
  3. ctools/api_result.py +55 -0
  4. ctools/application.py +386 -0
  5. ctools/b64.py +7 -0
  6. ctools/bashPath.py +13 -0
  7. ctools/bottle_web_base.py +169 -0
  8. ctools/bottle_webserver.py +143 -0
  9. ctools/bottle_websocket.py +75 -0
  10. ctools/browser_element_tools.py +314 -0
  11. ctools/call.py +71 -0
  12. ctools/cftp.py +74 -0
  13. ctools/cjson.py +54 -0
  14. ctools/ckafka.py +159 -0
  15. ctools/compile_tools.py +18 -0
  16. ctools/console.py +55 -0
  17. ctools/coord_trans.py +127 -0
  18. ctools/credis.py +111 -0
  19. ctools/cron_lite.py +252 -0
  20. ctools/ctoken.py +34 -0
  21. ctools/cword.py +30 -0
  22. ctools/czip.py +130 -0
  23. ctools/database.py +185 -0
  24. ctools/date_utils.py +43 -0
  25. ctools/dict_wrapper.py +20 -0
  26. ctools/douglas_rarefy.py +136 -0
  27. ctools/download_tools.py +57 -0
  28. ctools/enums.py +4 -0
  29. ctools/ex.py +31 -0
  30. ctools/excelOpt.py +36 -0
  31. ctools/html_soup.py +35 -0
  32. ctools/http_utils.py +24 -0
  33. ctools/images_tools.py +27 -0
  34. ctools/imgDialog.py +44 -0
  35. ctools/metrics.py +131 -0
  36. ctools/mqtt_utils.py +289 -0
  37. ctools/obj.py +20 -0
  38. ctools/pacth.py +74 -0
  39. ctools/plan_area_tools.py +97 -0
  40. ctools/process_pool.py +36 -0
  41. ctools/pty_tools.py +72 -0
  42. ctools/resource_bundle_tools.py +121 -0
  43. ctools/rsa.py +70 -0
  44. ctools/screenshot_tools.py +127 -0
  45. ctools/sign.py +20 -0
  46. ctools/sm_tools.py +49 -0
  47. ctools/snow_id.py +76 -0
  48. ctools/str_diff.py +20 -0
  49. ctools/string_tools.py +85 -0
  50. ctools/sys_info.py +157 -0
  51. ctools/sys_log.py +89 -0
  52. ctools/thread_pool.py +35 -0
  53. ctools/upload_tools.py +40 -0
  54. ctools/win_canvas.py +83 -0
  55. ctools/win_control.py +106 -0
  56. ctools/word_fill.py +562 -0
  57. ctools/word_fill_entity.py +46 -0
  58. ctools/work_path.py +69 -0
  59. {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.2.dist-info}/METADATA +1 -1
  60. gomyck_tools-1.3.2.dist-info/RECORD +62 -0
  61. gomyck_tools-1.3.2.dist-info/top_level.txt +1 -0
  62. gomyck_tools-1.3.1.dist-info/RECORD +0 -4
  63. gomyck_tools-1.3.1.dist-info/top_level.txt +0 -1
  64. {gomyck_tools-1.3.1.dist-info → gomyck_tools-1.3.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,143 @@
1
+ import sys
2
+ from socketserver import ThreadingMixIn
3
+ from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server
4
+
5
+ from bottle import ServerAdapter, Bottle, template, static_file, abort, redirect, response
6
+
7
+ from ctools import sys_info
8
+
9
+ """
10
+ module_names = list(globals().keys())
11
+ def get_modules():
12
+ mods = []
13
+ for modname in module_names:
14
+ if modname == 'base' or modname == 'online' or modname.startswith('__') or modname == 'importlib': continue
15
+ module = globals()[modname]
16
+ mods.append(module)
17
+ return mods
18
+
19
+ def get_ws_modules():
20
+ from . import websocket
21
+ return [websocket]
22
+ """
23
+
24
+ """
25
+ from ctools import bottle_web_base, token, bottle_webserver
26
+ from ctools.api_result import R
27
+
28
+ secret_key = "xxx"
29
+ app = bottle_web_base.init_app('子模块写 context_path, 主模块就不用写任何东西')
30
+
31
+ # 通用的鉴权方法
32
+ @bottle_web_base.before_intercept(0)
33
+ def token_check():
34
+ return bottle_web_base.common_auth_verify(secret_key)
35
+
36
+ @app.post('/login')
37
+ def login(params):
38
+ return R.ok(token.gen_token({'username': 'xxx'}, secret_key, 3600))
39
+
40
+ @app.get('/demo')
41
+ @bottle_web_base.rule('DOC:DOWNLOAD')
42
+ def demo(params):
43
+ print(params)
44
+
45
+ main_app = bottle_webserver.init_bottle() # 这里可以传 APP 当做主模块, 但是 context_path 就不好使了, 上下文必须是 /
46
+ main_app.mount(app.context_path, app)
47
+ main_app.set_index(r'轨迹点位压缩.html')
48
+ main_app.run()
49
+ """
50
+
51
+ _default_port = 8888
52
+
53
+ class CBottle:
54
+
55
+ def __init__(self, bottle: Bottle, port=_default_port, quiet=False):
56
+ self.port = port
57
+ self.quiet = quiet
58
+ self.bottle = bottle
59
+ self.index_root = './'
60
+ self.index_filename = 'index.html'
61
+ self.is_tpl = False
62
+ self.tmp_args = {}
63
+ self.redirect_url = None
64
+ self.static_root = './static'
65
+ self.download_root = './download'
66
+
67
+ @self.bottle.route(['/', '/index'])
68
+ def index():
69
+ try:
70
+ if self.redirect_url: return redirect(self.redirect_url)
71
+ if self.is_tpl: return template(f"{self.index_root}/{self.index_filename}", self.tmp_args)
72
+ return static_file(filename=self.index_filename, root=self.index_root)
73
+ except FileNotFoundError:
74
+ abort(404, "File not found...")
75
+
76
+ @self.bottle.route('/static/<filepath:path>')
77
+ def static(filepath):
78
+ try:
79
+ return static_file(filepath, root=self.static_root)
80
+ except FileNotFoundError:
81
+ abort(404, "File not found...")
82
+
83
+ @self.bottle.route('/download/<filepath:path>')
84
+ def download(filepath):
85
+ return static_file(filepath, root=self.download_root, download=True)
86
+
87
+ @self.bottle.route('/favicon.ico')
88
+ def favicon():
89
+ response.content_type = 'image/svg+xml'
90
+ svg_icon = '''<?xml version="1.0" encoding="UTF-8"?>
91
+ <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
92
+ <circle cx="16" cy="16" r="14" fill="#007bff"/>
93
+ <path d="M16 8a8 8 0 0 0-8 8h2a6 6 0 0 1 12 0h2a8 8 0 0 0-8-8z" fill="white"/>
94
+ <circle cx="16" cy="20" r="2" fill="white"/>
95
+ </svg>
96
+ '''
97
+ return svg_icon
98
+
99
+ def run(self):
100
+ http_server = WSGIRefServer(port=self.port)
101
+ print('Click the link below to open the service homepage %s' % '\n \t\t http://localhost:%s \n \t\t http://%s:%s' % (self.port, sys_info.get_local_ipv4(), self.port), file=sys.stderr)
102
+ self.bottle.run(server=http_server, quiet=self.quiet)
103
+
104
+ def set_index(self, filename='index.html', root='./', is_tpl=False, redirect_url=None, **kwargs):
105
+ self.index_root = root
106
+ self.index_filename = filename
107
+ self.is_tpl = is_tpl
108
+ self.redirect_url = redirect_url
109
+ self.tmp_args = kwargs
110
+
111
+ def set_static(self, root='./static'):
112
+ self.static_root = root
113
+
114
+ def set_download(self, root='./download'):
115
+ self.download_root = root
116
+
117
+ def mount(self, context_path, app, **kwargs):
118
+ self.bottle.mount(context_path, app, **kwargs)
119
+
120
+ def init_bottle(app:Bottle=None, port=_default_port, quiet=False) -> CBottle:
121
+ bottle = app or Bottle()
122
+ return CBottle(bottle, port, quiet)
123
+
124
+ class ThreadedWSGIServer(ThreadingMixIn, WSGIServer):
125
+ daemon_threads = True
126
+
127
+ class CustomWSGIHandler(WSGIRequestHandler):
128
+ def log_request(*args, **kw): pass
129
+
130
+ class WSGIRefServer(ServerAdapter):
131
+
132
+ def __init__(self, host='0.0.0.0', port=_default_port):
133
+ super().__init__(host, port)
134
+ self.server = None
135
+
136
+ def run(self, handler):
137
+ req_handler = WSGIRequestHandler
138
+ if self.quiet: req_handler = CustomWSGIHandler
139
+ self.server = make_server(self.host, self.port, handler, server_class=ThreadedWSGIServer, handler_class=req_handler)
140
+ self.server.serve_forever()
141
+
142
+ def stop(self):
143
+ self.server.shutdown()
@@ -0,0 +1,75 @@
1
+ from bottle import ServerAdapter, Bottle
2
+ from geventwebsocket.handler import WebSocketHandler
3
+
4
+ from ctools import sys_log
5
+
6
+ """
7
+ module_names = list(globals().keys())
8
+ def get_modules():
9
+ mods = []
10
+ for modname in module_names:
11
+ if modname == 'base' or modname == 'online' or modname.startswith('__') or modname == 'importlib': continue
12
+ module = globals()[modname]
13
+ mods.append(module)
14
+ return mods
15
+
16
+ def get_ws_modules():
17
+ from . import websocket
18
+ return [websocket]
19
+ """
20
+
21
+ """
22
+ ws_app = bottle_web_base.init_app('/websocket_demo')
23
+
24
+ @ws_app.route('/script_debug', apply=[websocket])
25
+ @bottle_web_base.rule('DOC:DOWNLOAD')
26
+ def script_debug(ws: WebSocket):
27
+ print(123)
28
+
29
+ socket_app = bottle_websocket.init_bottle()
30
+ socket_app.mount(app.context_path, ws_app)
31
+ socket_app.run()
32
+ """
33
+
34
+ _default_port = 8887
35
+
36
+ class CBottle:
37
+
38
+ def __init__(self, bottle: Bottle, port=_default_port, quiet=False):
39
+ self.port = port
40
+ self.quiet = quiet
41
+ self.bottle = bottle
42
+
43
+ def run(self):
44
+ socket_server = WebSocketServer(port=self.port)
45
+ self.bottle.run(server=socket_server, quiet=self.quiet)
46
+
47
+ def mount(self, context_path, app):
48
+ self.bottle.mount(context_path, app)
49
+
50
+ def init_bottle(port=_default_port, quiet=False) -> CBottle:
51
+ bottle = Bottle()
52
+ return CBottle(bottle, port, quiet)
53
+
54
+ class CustomWebSocketHandler(WebSocketHandler):
55
+ def log_request(self):
56
+ if '101' not in str(self.status):
57
+ log_msg = self.format_request()
58
+ for nk in sys_log.neglect_keywords:
59
+ if nk in log_msg:
60
+ return
61
+ self.logger.info(log_msg)
62
+
63
+ class WebSocketServer(ServerAdapter):
64
+
65
+ def __init__(self, host='0.0.0.0', port=_default_port):
66
+ super().__init__(host, port)
67
+ self.server = None
68
+
69
+ def run(self, handler):
70
+ from gevent import pywsgi
71
+ self.server = pywsgi.WSGIServer((self.host, self.port), handler, handler_class=CustomWebSocketHandler)
72
+ self.server.serve_forever()
73
+
74
+ def stop(self):
75
+ self.server.stop()
@@ -0,0 +1,314 @@
1
+ import os
2
+ import time
3
+
4
+ from business.common.constant import Scheduler
5
+ from lxml import etree
6
+ from lxml import html
7
+ from pynput import keyboard
8
+ from selenium import webdriver
9
+ from selenium.common import NoSuchElementException, WebDriverException, NoSuchWindowException
10
+ from selenium.webdriver.chrome.options import Options
11
+ from selenium.webdriver.chrome.service import Service
12
+ from selenium.webdriver.common.by import By
13
+ from urllib3.exceptions import MaxRetryError
14
+
15
+ from ctools import application, string_tools
16
+
17
+ keyboard_listener = None
18
+ ctrl_pressed = None
19
+ g_driver = None
20
+ g_callback = None
21
+ g_result = []
22
+ g_quite_flag = False
23
+ picture_path = ""
24
+
25
+ def get_hovered_element_html(url, explore, callback):
26
+ global g_driver, g_callback, g_result
27
+ g_callback = callback
28
+ g_driver = explore.init()
29
+ driver = g_driver
30
+ driver.maximize_window()
31
+ driver.get(url)
32
+ start_keyboard_listener()
33
+ handle_arr = [driver.current_window_handle]
34
+ close_page_flag = False
35
+ while g_driver and not g_quite_flag:
36
+ try:
37
+ try:
38
+ if len(driver.window_handles) == 0:
39
+ break_func()
40
+ break
41
+ except WebDriverException:
42
+ break_func()
43
+ break
44
+ except MaxRetryError:
45
+ break_func()
46
+ break
47
+ try:
48
+ driver.find_element(value="ck-overlay")
49
+ except NoSuchElementException as e:
50
+ driver.execute_script(explore.listen_script)
51
+ except Exception:
52
+ pass
53
+ for handle in driver.window_handles:
54
+ if handle in handle_arr: continue
55
+ if not close_page_flag: driver.execute_script(overlay_script)
56
+ handle_arr.append(handle)
57
+ driver.switch_to.window(handle)
58
+ try:
59
+ driver.execute_script(explore.listen_script)
60
+ except Exception:
61
+ continue
62
+ close_page_flag = False
63
+ time.sleep(0.5)
64
+ cursor_html = driver.execute_script(explore.return_script)
65
+ if cursor_html:
66
+ g_result.clear()
67
+ cursor_dom = html.fragment_fromstring(cursor_html)
68
+ cursor_dom.set("ck-flag", "ck")
69
+ enhance_cursor_html = etree.tostring(cursor_dom, method="html", encoding="unicode")
70
+ page_html = driver.page_source.replace(cursor_html, enhance_cursor_html)
71
+ page_dom_tree = etree.ElementTree(etree.HTML(page_html))
72
+ match_dom = page_dom_tree.xpath('//*[@ck-flag="ck"]')
73
+ for ck_element in match_dom:
74
+ ck_xpath = page_dom_tree.getpath(ck_element)
75
+ #print('XPATH IS {}'.format(ck_xpath))
76
+ try:
77
+ ele = driver.find_element(By.XPATH, ck_xpath)
78
+ #print('XPATH_HTML: {}'.format(ele.get_attribute('outerHTML')))
79
+ except Exception:
80
+ pass
81
+ g_result.append(ck_xpath)
82
+ except NoSuchWindowException as e:
83
+ close_page_flag = True
84
+ handle_arr = []
85
+ try:
86
+ driver.switch_to.window(driver.window_handles[-1])
87
+ driver.execute_script(hide_shade)
88
+ except Exception:
89
+ print('切换HANDLE失败:', e)
90
+ break_func()
91
+ break
92
+ except Exception as e:
93
+ print('全局错误:', e)
94
+ break_func()
95
+ break
96
+ try:
97
+ g_driver.quit()
98
+ except Exception as e:
99
+ pass
100
+
101
+ def break_func():
102
+ keyboard_listener.stop()
103
+ g_callback(None)
104
+
105
+ def on_press(key):
106
+ global keyboard_listener, ctrl_pressed, g_driver, g_quite_flag, picture_path
107
+ if key == keyboard.Key.ctrl_l and not ctrl_pressed:
108
+ ctrl_pressed = True
109
+ elif key == keyboard.Key.esc:
110
+ g_result.clear()
111
+ keyboard_listener.stop()
112
+ g_quite_flag = True
113
+ g_callback(None)
114
+ elif hasattr(key, 'vk') and key.vk == 192 and ctrl_pressed:
115
+ # print("g_result: %s" % g_result)
116
+ if g_result:
117
+ try:
118
+ select_element = g_driver.find_element(By.XPATH, g_result[0])
119
+ picture_path = "%s.png" % os.path.join(application.Server.screenshotPath, "element-"+string_tools.get_uuid())
120
+ select_element.screenshot(picture_path)
121
+ except Exception:
122
+ pass
123
+
124
+ keyboard_listener.stop()
125
+ g_quite_flag = True
126
+ g_callback(g_result)
127
+
128
+ def on_release(key):
129
+ global ctrl_pressed
130
+ if key == keyboard.Key.ctrl_l:
131
+ ctrl_pressed = False
132
+
133
+ def start_keyboard_listener():
134
+ global keyboard_listener
135
+ keyboard_listener = keyboard.Listener(on_press=on_press, on_release=on_release)
136
+ keyboard_listener.start()
137
+
138
+
139
+ overlay_script = """
140
+ // 创建遮罩层
141
+ var shade = document.createElement('div');
142
+ shade.id = 'ck-shade-parent';
143
+ shade.style.position = 'fixed';
144
+ shade.style.top = '0';
145
+ shade.style.left = '0';
146
+ shade.style.width = '100%';
147
+ shade.style.height = '100%';
148
+ shade.style.backgroundColor = '#000'; // 使用纯色背景
149
+ shade.style.filter = 'alpha(opacity=60)'; // 设置透明度,IE8-IE9
150
+ shade.style.opacity = '0.8'; // 设置透明度,现代浏览器
151
+ shade.style.zIndex = '9999';
152
+ document.body.appendChild(shade);
153
+
154
+ // 创建覆盖内容
155
+ var overlayContent = document.createElement('div');
156
+ overlayContent.id = 'ck-shade-oc';
157
+ overlayContent.className = 'overlay-content';
158
+ overlayContent.style.position = 'absolute';
159
+ overlayContent.style.top = '50%';
160
+ overlayContent.style.left = '50%';
161
+ overlayContent.style.transform = 'translate(-50%, -50%)';
162
+ overlayContent.style.backgroundColor = 'white';
163
+ overlayContent.style.padding = '20px';
164
+ overlayContent.style.borderRadius = '8px';
165
+ overlayContent.style.textAlign = 'center';
166
+ shade.appendChild(overlayContent);
167
+
168
+ // 创建消息内容
169
+ var message = document.createElement('p');
170
+ message.id = 'ck-shade-msg';
171
+ message.innerText = '当前页面未激活,请关闭激活状态的录制页面';
172
+ message.style.color = '#000'; // 设置文本颜色为黑色
173
+ message.style.fontSize = '16px'; // 设置文本大小
174
+ overlayContent.appendChild(message);
175
+
176
+ """
177
+
178
+ hide_shade = """
179
+ var shade = document.getElementById('ck-shade-parent');
180
+ if (shade) {
181
+ shade.parentNode.removeChild(shade);
182
+ }
183
+ """
184
+
185
+ class IE:
186
+
187
+ @staticmethod
188
+ def init():
189
+ ie_options = webdriver.IeOptions()
190
+ ie_options.ignore_protected_mode_settings = True
191
+ ie_options.ignore_zoom_level = True
192
+ ie_options.require_window_focus = True
193
+ return webdriver.Ie(options=ie_options)
194
+
195
+ listen_script = """
196
+ var overlay = document.createElement('div');
197
+ overlay.id = 'ck-overlay';
198
+ overlay.style.position = 'absolute';
199
+ overlay.style.border = '2px solid red';
200
+ overlay.style.pointerEvents = 'none';
201
+ overlay.style.zIndex = '9999';
202
+ document.body.appendChild(overlay);
203
+ var addEvent = function(elem, type, eventHandle) {
204
+ if (elem == null || typeof(elem) == 'undefined') return;
205
+ if (elem.addEventListener) {
206
+ elem.addEventListener(type, eventHandle, false);
207
+ } else if (elem.attachEvent) {
208
+ elem.attachEvent('on' + type, eventHandle);
209
+ } else {
210
+ elem['on' + type] = eventHandle;
211
+ }
212
+ };
213
+
214
+ addEvent(document, 'mousemove', function(e) {
215
+ e = e || window.event;
216
+ var element = document.elementFromPoint(e.clientX, e.clientY);
217
+ window.hoveredElement = element;
218
+ if (element === overlay) return;
219
+ var rect = element.getBoundingClientRect();
220
+ overlay.style.left = (rect.left + (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft) - 5) + 'px';
221
+ overlay.style.top = (rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop) - 5) + 'px';
222
+ overlay.style.width = (element.offsetWidth + 10) + 'px';
223
+ overlay.style.height = (element.offsetHeight + 10) + 'px';
224
+ });
225
+ """
226
+
227
+ return_script = """
228
+ return window.hoveredElement ? window.hoveredElement.outerHTML : null;
229
+ """
230
+
231
+ overlay_script = overlay_script
232
+
233
+
234
+ class Chrome:
235
+
236
+ @staticmethod
237
+ def init():
238
+ option = Options()
239
+ option.binary_location = Scheduler.CHROME_PATH
240
+ service = Service(Scheduler.CHROME_DRIVER_PATH)
241
+ return webdriver.Chrome(options=option, service=service)
242
+
243
+ listen_script = """
244
+ var overlay = document.createElement('div');
245
+ overlay.id = 'ck-overlay';
246
+ overlay.style.position = 'absolute';
247
+ overlay.style.border = '2px solid red';
248
+ overlay.style.pointerEvents = 'none';
249
+ overlay.style.zIndex = '9998';
250
+ document.body.appendChild(overlay);
251
+
252
+ function throttle(func, limit) {
253
+ let inThrottle;
254
+ return function() {
255
+ const args = arguments,
256
+ context = this;
257
+ if (!inThrottle) {
258
+ func.apply(context, args);
259
+ inThrottle = true;
260
+ setTimeout(() => inThrottle = false, limit);
261
+ }
262
+ };
263
+ }
264
+
265
+ document.addEventListener('mousemove', throttle(function(e) {
266
+ var element = document.elementFromPoint(e.clientX, e.clientY);
267
+ window.hoveredElement = element;
268
+ if(element.id.indexOf('ck-shade') !== -1) return;
269
+ if (window.hoveredElement) {
270
+ var rect = element.getBoundingClientRect();
271
+ overlay.style.left = (rect.left + window.pageXOffset - 5) + 'px';
272
+ overlay.style.top = (rect.top + window.pageYOffset - 5) + 'px';
273
+ overlay.style.width = (element.offsetWidth + 10) + 'px';
274
+ overlay.style.height = (element.offsetHeight + 10) + 'px';
275
+ }
276
+ }, 50));
277
+ """
278
+
279
+ return_script = """
280
+ return window.hoveredElement ? window.hoveredElement.outerHTML : null;
281
+ """
282
+
283
+ overlay_script = overlay_script
284
+
285
+
286
+ def callback(xpath):
287
+ print("Hovered Element XPATH IS :", xpath)
288
+
289
+ def get_element(url: str = None, explore: str = "chrome"):
290
+ global keyboard_listener, ctrl_pressed, g_driver, g_callback, g_result, g_quite_flag, picture_path
291
+ keyboard_listener = None
292
+ ctrl_pressed = None
293
+ g_driver = None
294
+ g_callback = None
295
+ g_result = []
296
+ g_quite_flag = False
297
+ picture_path = ""
298
+
299
+ if explore == "chrome":
300
+ explore = Chrome
301
+ else:
302
+ explore = IE
303
+
304
+ if "http" not in url[:5]:
305
+ url = "http://%s" % url
306
+
307
+ get_hovered_element_html(url, explore, callback)
308
+
309
+ return g_result, picture_path
310
+
311
+ if __name__ == "__main__":
312
+ # from explore_record_core import get_hovered_element_html, Chrome
313
+ g_result, picture_path = get_element("weibo.com")
314
+ print(g_result, picture_path)
ctools/call.py ADDED
@@ -0,0 +1,71 @@
1
+ import sched
2
+ import threading
3
+ import time
4
+ from functools import wraps
5
+
6
+ # annotation
7
+ def once(func):
8
+ """
9
+ decorator to initialize a function once
10
+ :param func: function to be initialized
11
+ :return: the real decorator for return the result
12
+ """
13
+ initialized = False
14
+ res = None
15
+
16
+ def wrapper(*args, **kwargs):
17
+ nonlocal initialized, res
18
+ if not initialized:
19
+ res = func(*args, **kwargs)
20
+ initialized = True
21
+ return res
22
+ else:
23
+ return res
24
+
25
+ return wrapper
26
+
27
+ # annotation
28
+ def init(func):
29
+ """
30
+ decorator to initialize a function automic
31
+ :param func: function to be initialized
32
+ :return: the real decorator for return the result
33
+ """
34
+ res = func()
35
+
36
+ def wrapper():
37
+ return res
38
+
39
+ return wrapper
40
+
41
+ # annotation
42
+ def schd(interval_seconds, start_by_call: bool = False):
43
+ start_flag = False
44
+ run_flag = False
45
+ scheduler = sched.scheduler(time.time, time.sleep)
46
+
47
+ def decorator(func):
48
+ @wraps(func)
49
+ def wrapper(*args, **kwargs):
50
+ nonlocal start_flag
51
+ if start_by_call and not start_flag:
52
+ start_flag = True
53
+ threading.Thread(target=wrapper, args=args, kwargs=kwargs, daemon=True).start()
54
+ return
55
+
56
+ def job():
57
+ func(*args, **kwargs)
58
+ scheduler.enter(interval_seconds, 1, job)
59
+
60
+ nonlocal run_flag
61
+ if not run_flag:
62
+ scheduler.enter(interval_seconds, 1, job)
63
+ run_flag = True
64
+ scheduler.run()
65
+ else:
66
+ func(*args, **kwargs)
67
+
68
+ if not start_by_call: threading.Thread(target=wrapper, daemon=True).start()
69
+ return wrapper
70
+
71
+ return decorator
ctools/cftp.py ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
+ __author__ = 'haoyang'
4
+ __date__ = '2025/1/24 08:53'
5
+
6
+ import io
7
+ import time
8
+ from ftplib import FTP, error_perm, error_temp
9
+
10
+ class CFTP:
11
+ """
12
+ with open('xx/xx.md', 'rb') as file:
13
+ ftp_host = 'x.x.x.x'
14
+ ftp_username = 'x'
15
+ ftp_password = 'x'
16
+ CFTP(ftp_host, ftp_username, ftp_password).upload_file_to_ftp('xx.md', file)
17
+ """
18
+ def __init__(self, host, username, password, timeout=30, max_retries=3, retry_delay=5):
19
+ self.host = host
20
+ self.username = username
21
+ self.password = password
22
+ self.ftp:FTP = None
23
+ self.timeout = timeout
24
+ self.max_retries = max_retries
25
+ self.retry_delay = retry_delay
26
+
27
+ def upload_file_to_ftp(self, file_name, file:io.BytesIO, ftp_directory='/'):
28
+ if not file_name: raise Exception('文件名不能为空')
29
+ if not file: raise Exception('文件不能为空')
30
+ for attempt in range(self.max_retries):
31
+ try:
32
+ if not self.ftp:
33
+ ftp = FTP(self.host, timeout=self.timeout)
34
+ ftp.login(self.username, self.password)
35
+ ftp.set_pasv(True)
36
+ self.ftp = ftp
37
+ try:
38
+ self.ftp.cwd(ftp_directory)
39
+ except error_perm:
40
+ print(f"FTP 目录不存在:{ftp_directory}")
41
+ self.ftp.mkd(ftp_directory)
42
+ print(f"FTP 目录创建成功:{ftp_directory}, 正在切换到目录:{ftp_directory}")
43
+ self.ftp.cwd(ftp_directory)
44
+ print(f"正在上传文件:{file_name}")
45
+ self.ftp.storbinary(f"STOR {file_name}", file)
46
+ self.ftp.quit()
47
+ print(f"文件成功上传到 FTP: {file_name}")
48
+ return
49
+ except (error_perm, error_temp) as e:
50
+ try:
51
+ self.ftp.quit()
52
+ except Exception:
53
+ pass
54
+ self.ftp = None
55
+ print(f"FTP 错误:{e}")
56
+ if attempt < self.max_retries - 1:
57
+ print(f"正在尝试重连... 第 {attempt + 1} 次重试...")
58
+ time.sleep(self.retry_delay)
59
+ else:
60
+ print("重试次数已用尽,上传失败。")
61
+ raise
62
+ except Exception as e:
63
+ try:
64
+ self.ftp.quit()
65
+ except Exception:
66
+ pass
67
+ self.ftp = None
68
+ print(f"连接或上传出现异常:{e}")
69
+ if attempt < self.max_retries - 1:
70
+ print(f"正在尝试重连... 第 {attempt + 1} 次重试...")
71
+ time.sleep(self.retry_delay)
72
+ else:
73
+ print("重试次数已用尽,上传失败。")
74
+ raise