ryry-cli 6.18__tar.gz → 6.19__tar.gz

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 (32) hide show
  1. {ryry_cli-6.18/ryry_cli.egg-info → ryry_cli-6.19}/PKG-INFO +1 -1
  2. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/constant.py +2 -2
  3. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/store.py +1 -1
  4. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/upload.py +193 -20
  5. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/utils.py +219 -217
  6. {ryry_cli-6.18 → ryry_cli-6.19/ryry_cli.egg-info}/PKG-INFO +1 -1
  7. {ryry_cli-6.18 → ryry_cli-6.19}/setup.py +1 -1
  8. {ryry_cli-6.18 → ryry_cli-6.19}/LICENSE +0 -0
  9. {ryry_cli-6.18 → ryry_cli-6.19}/README.md +0 -0
  10. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/__init__.py +0 -0
  11. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/daemon_base.py +0 -0
  12. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/daemon_manager.py +0 -0
  13. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/main.py +0 -0
  14. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/proxy_manager.py +0 -0
  15. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/ryry_server_socket.py +0 -0
  16. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/ryry_service.py +0 -0
  17. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/ryry_webapi.py +0 -0
  18. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/ryry_widget.py +0 -0
  19. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/script_template/__init__.py +0 -0
  20. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/script_template/daemon.py +0 -0
  21. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/script_template/main.py +0 -0
  22. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/script_template/run.py +0 -0
  23. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/server_func.py +0 -0
  24. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/shared_memory.py +0 -0
  25. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/task.py +0 -0
  26. {ryry_cli-6.18 → ryry_cli-6.19}/ryry/taskUtils.py +0 -0
  27. {ryry_cli-6.18 → ryry_cli-6.19}/ryry_cli.egg-info/SOURCES.txt +0 -0
  28. {ryry_cli-6.18 → ryry_cli-6.19}/ryry_cli.egg-info/dependency_links.txt +0 -0
  29. {ryry_cli-6.18 → ryry_cli-6.19}/ryry_cli.egg-info/entry_points.txt +0 -0
  30. {ryry_cli-6.18 → ryry_cli-6.19}/ryry_cli.egg-info/requires.txt +0 -0
  31. {ryry_cli-6.18 → ryry_cli-6.19}/ryry_cli.egg-info/top_level.txt +0 -0
  32. {ryry_cli-6.18 → ryry_cli-6.19}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 6.18
3
+ Version: 6.19
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -1,6 +1,6 @@
1
1
  #!!!!! do not change this file !!!!!
2
- app_version="6.18"
3
- app_bulld_anchor="Noh_2026-07-05 17:02:09.601187"
2
+ app_version="6.19"
3
+ app_bulld_anchor="Noh_2026-07-09 01:20:20.729214"
4
4
  app_name="ryry-cli"
5
5
  import sys, os
6
6
  if getattr(sys, 'frozen', False):
@@ -177,7 +177,7 @@ def extend():
177
177
  _genExtend()
178
178
  elif read_data["extend"].get("device_id", "") != read_data.get("deviceInfo", {}).get("device_id", ""):
179
179
  _genExtend()
180
- elif read_data["extend"].get("app", "") == "ryry-cli 6.18":
180
+ elif read_data["extend"].get("app", "") == "ryry-cli 6.19":
181
181
  _genExtend()
182
182
  elif any(k not in read_data["extend"] for k in ["device_id", "host_name", "cpu", "memory", "disk", "gpu"]):
183
183
  _genExtend()
@@ -78,8 +78,8 @@ def upload(src, taskUUID=None, needTranscode=False, keepItAlways=False, addition
78
78
  os.remove(newSrc)
79
79
  return ossurl
80
80
 
81
- def uploadByDomain(src, targetDomain,
82
- taskUUID, keepItAlways=False,
81
+ def uploadByDomain(src, targetDomain,
82
+ taskUUID, keepItAlways=False,
83
83
  needTranscode=False, additionalUrl=False):
84
84
  import os
85
85
  from pathlib import Path
@@ -90,23 +90,33 @@ def uploadByDomain(src, targetDomain,
90
90
  ext = os.path.splitext(file_name)[-1][1:]
91
91
 
92
92
  parsed_domain, upload_path = parseDomainAndPath(targetDomain)
93
- if isMekoConfig(parsed_domain):
94
- return uploadToMeko(src, ext=ext)
95
-
93
+
94
+ s3_config = getS3Config(parsed_domain)
95
+ if s3_config:
96
+ if isS3ConfigReady(s3_config):
97
+ new_file_name = ''.join(str(uuid.uuid4()).split('-'))
98
+ return uploadToS3(src, f"{new_file_name}.{ext}", s3_config, upload_path)
99
+ return uploadByApiFallback(src, ext, s3_config.get("fallback"),
100
+ keepItAlways=keepItAlways,
101
+ needTranscode=needTranscode,
102
+ additionalUrl=additionalUrl)
103
+
96
104
  oss_config = getOssConfig(parsed_domain)
97
105
  if oss_config:
98
106
  new_file_name = ''.join(str(uuid.uuid4()).split('-'))
99
107
  return uploadToOss(src, f"{new_file_name}.{ext}", oss_config, upload_path)
100
-
108
+
109
+ if isMekoConfig(parsed_domain):
110
+ return uploadToMeko(src, ext=ext)
111
+
101
112
  ftp_config = getFtpConfig(parsed_domain)
102
113
  if ftp_config:
103
114
  return ftpUpload(src, file_name, ftp_config, upload_path)
104
115
 
105
- from ryry import ryry_webapi
106
- return ryry_webapi.upload(src, ext,
107
- keepItAlways=keepItAlways,
108
- needTranscode=needTranscode,
109
- additionalUrl=additionalUrl)
116
+ return uploadByApiFallback(src, ext, "dalipen_api",
117
+ keepItAlways=keepItAlways,
118
+ needTranscode=needTranscode,
119
+ additionalUrl=additionalUrl)
110
120
 
111
121
  def parseDomainAndPath(targetDomain):
112
122
  from urllib.parse import urlparse
@@ -131,13 +141,29 @@ def parseDomainAndPath(targetDomain):
131
141
  def uploadToMeko(file_path: str, ext: str = ".jpg") -> str:
132
142
  import requests
133
143
  url = "https://api.mekoapp.com/upload/create"
134
-
144
+
135
145
  with open(file_path, "rb") as f:
136
146
  files = {"file": (file_path.split("/")[-1], f)}
137
147
  data = {"ext": ext}
138
148
  response = requests.post(url, files=files, data=data)
139
149
  response.raise_for_status()
140
- return response.json()["url"]
150
+ result = response.json()
151
+ if "url" in result:
152
+ return result["url"]
153
+ if result.get("code") == 0 and result.get("data"):
154
+ return result["data"]
155
+ raise Exception(f"meko upload failed: {result}")
156
+
157
+ def uploadByApiFallback(src, ext, fallback,
158
+ keepItAlways=False, needTranscode=False, additionalUrl=False):
159
+ if fallback == "meko_api":
160
+ return uploadToMeko(src, ext=ext)
161
+
162
+ from ryry import ryry_webapi
163
+ return ryry_webapi.upload(src, ext,
164
+ keepItAlways=keepItAlways or fallback == "dalipen_datanet_api",
165
+ needTranscode=needTranscode,
166
+ additionalUrl=additionalUrl)
141
167
 
142
168
  def uploadToOss(file, new_file_name, oss_config, upload_path=""):
143
169
  import hashlib
@@ -182,6 +208,35 @@ def uploadToOss(file, new_file_name, oss_config, upload_path=""):
182
208
  else:
183
209
  raise Exception(f"OSS upload failed: {response.status_code}")
184
210
 
211
+ def _safe_join_path(*parts):
212
+ clean_parts = []
213
+ for part in parts:
214
+ if not part:
215
+ continue
216
+ clean = str(part).strip("/")
217
+ if clean:
218
+ clean_parts.append(clean)
219
+ return "/".join(clean_parts)
220
+
221
+ def _s3_object_key(new_file_name, s3_config, upload_path=""):
222
+ if upload_path:
223
+ return _safe_join_path(upload_path, new_file_name)
224
+ return _safe_join_path("temp", new_file_name)
225
+
226
+ def uploadToS3(file, new_file_name, s3_config, upload_path=""):
227
+ bucket = s3_config["bucket"]
228
+ content_type = s3_config.get("content_type", "application/octet-stream")
229
+ object_key = _s3_object_key(new_file_name, s3_config, upload_path)
230
+ s3_client = getS3Client(s3_config)
231
+ with open(file, "rb") as f:
232
+ s3_client.put_object(
233
+ Bucket=bucket,
234
+ Key=object_key,
235
+ Body=f,
236
+ ContentType=content_type,
237
+ )
238
+ return f"{s3_config['domain'].rstrip('/')}/{object_key}"
239
+
185
240
  def deepFtpUpload(file, new_file_name, ftp, writepath, readpath):
186
241
  try:
187
242
  safe_cwd(ftp, writepath)
@@ -280,6 +335,32 @@ ftp_219_136_123_179={
280
335
  }
281
336
 
282
337
  from ryry import utils
338
+ _s3_client_cache = {}
339
+ def getS3Client(s3_config):
340
+ import boto3
341
+ from botocore.config import Config
342
+
343
+ a_k_i, s_a_k = utils._decode_key(utils.RK11), utils._decode_key(utils.RK22)
344
+ endpoint = s3_config["endpoint"].rstrip("/")
345
+ region = s3_config.get("region", "auto")
346
+ cache_key = (endpoint, region, a_k_i)
347
+ if cache_key not in _s3_client_cache:
348
+ _s3_client_cache[cache_key] = boto3.client(
349
+ "s3",
350
+ endpoint_url=endpoint,
351
+ aws_access_key_id=a_k_i,
352
+ aws_secret_access_key=s_a_k,
353
+ region_name=region,
354
+ config=Config(
355
+ signature_version="s3v4",
356
+ connect_timeout=10,
357
+ read_timeout=100,
358
+ retries={"max_attempts": 3, "mode": "standard"},
359
+ s3={"addressing_style": "path"},
360
+ ),
361
+ )
362
+ return _s3_client_cache[cache_key]
363
+
283
364
  oss_res_config = {
284
365
  "bucket": "p-res-gz",
285
366
  "endpoint": "oss-cn-guangzhou.aliyuncs.com",
@@ -298,6 +379,16 @@ oss_template_config = {
298
379
  "domain": "https://oss.zjtemplate.com"
299
380
  }
300
381
 
382
+ REP = utils._decode_key(utils.RK23)
383
+ def r2Config(bucket, domain, fallback="dalipen_api"):
384
+ return {
385
+ "bucket": bucket,
386
+ "endpoint": REP,
387
+ "region": "auto",
388
+ "domain": f"https://{domain}",
389
+ "fallback": fallback,
390
+ }
391
+
301
392
  def match_wildcard_domain(target_domain, pattern):
302
393
  if pattern.startswith("*."):
303
394
  suffix = pattern[1:] # 去掉 "*." 前缀
@@ -323,10 +414,19 @@ DOMAIN_CONFIG = {
323
414
  # "ftp://192.168.50.12/mnt/NAS/mcn/cache": {"type": "ftp", "config": ftp_192_168_50_12},
324
415
  # "ftp://183.6.90.205/mnt/NAS/mcn/cache": {"type": "ftp", "config": ftp_219_136_123_179},
325
416
  # "ftp://219.136.123.179/mnt/NAS/mcn/cache": {"type": "ftp", "config": ftp_219_136_123_179},
417
+ "app-upload.dalipen.com": {"type": "s3", "config": r2Config("p-app-upload", "app-upload.dalipen.com")},
418
+ "datanet.dalipen.com": {"type": "s3", "config": r2Config("p-crawler", "datanet.dalipen.com", "dalipen_datanet_api")},
419
+ "model.dalipen.com": {"type": "s3", "config": r2Config("p-model", "model.dalipen.com")},
420
+ "r2.dalipen.com": {"type": "s3", "config": r2Config("p-template", "r2.dalipen.com")},
421
+ "upload.dalipen.com": {"type": "s3", "config": r2Config("p-upload", "upload.dalipen.com"), "oversea": True},
422
+ "widget.dalipen.com": {"type": "s3", "config": r2Config("p-widget", "widget.dalipen.com")},
423
+ "app-upload.mekoapp.com": {"type": "s3", "config": r2Config("p-app-upload", "app-upload.mekoapp.com", "meko_api")},
424
+ "m.mekoapp.com": {"type": "s3", "config": r2Config("p-app-upload", "m.mekoapp.com", "meko_api")},
425
+ "upload.mekoapp.com": {"type": "s3", "config": r2Config("p-upload", "upload.mekoapp.com", "meko_api")},
326
426
  "res.zjtemplate.com": {"type": "oss", "config": oss_res_config},
327
- "upload.zjtemplate.com": {"type": "oss", "config": oss_upload_config},
427
+ "upload.zjtemplate.com": {"type": "oss", "config": oss_upload_config, "oversea": False},
328
428
  "oss.zjtemplate.com": {"type": "oss", "config": oss_template_config},
329
- "*.mekoapp.com": {"type": "", "config": {}},
429
+ "*.mekoapp.com": {"type": "meko_api", "config": {}},
330
430
  }
331
431
 
332
432
  FTP_REPLACEMENT = {
@@ -356,9 +456,24 @@ def getOssConfig(targetDomain):
356
456
  return config["config"]
357
457
  return None
358
458
 
459
+ def isS3ConfigReady(s3_config):
460
+ return all([
461
+ s3_config.get("bucket"),
462
+ s3_config.get("endpoint"),
463
+ s3_config.get("domain"),
464
+ ])
465
+
466
+ def getS3Config(targetDomain):
467
+ config = get_domain_config(targetDomain)
468
+ if config and config["type"] == "s3":
469
+ return config["config"]
470
+ return None
471
+
359
472
  def isMekoConfig(targetDomain):
360
473
  config = get_domain_config(targetDomain)
361
- if config:
474
+ if config and config["type"] == "meko_api":
475
+ return True
476
+ if config and config.get("config", {}).get("fallback") == "meko_api":
362
477
  return True
363
478
  return False
364
479
 
@@ -385,16 +500,74 @@ def getSubdomain(targetDomain):
385
500
  return None
386
501
 
387
502
  def getFirstSupportSubdomain():
388
- def get_network_hash():
503
+ def ping_ms(host, timeout=1):
504
+ import platform
505
+ import re
506
+ import subprocess
507
+ try:
508
+ if platform.system() == "Windows":
509
+ cmd = ["ping", "-n", "1", "-w", str(int(timeout * 1000)), host]
510
+ else:
511
+ cmd = ["ping", "-c", "1", host]
512
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
513
+ timeout=timeout + 0.5, text=True)
514
+ if result.returncode != 0:
515
+ return None
516
+ match = re.search(r"(?:time|时间)[=<]\s*([0-9.]+)\s*ms", result.stdout, re.IGNORECASE)
517
+ if match:
518
+ return float(match.group(1))
519
+ except:
520
+ pass
521
+ return None
522
+
523
+ def is_oversea_network():
524
+ baidu_ms = ping_ms("baidu.com", timeout=1)
525
+ if baidu_ms is not None and baidu_ms <= 80:
526
+ return False
527
+
528
+ google_ms = ping_ms("google.com", timeout=1)
529
+ if google_ms is None and baidu_ms is not None:
530
+ return False
531
+ if google_ms is not None and baidu_ms is None:
532
+ return True
533
+ if google_ms is not None and baidu_ms is not None:
534
+ return google_ms < baidu_ms
535
+ return None
536
+
537
+ def get_network_hash(oversea):
389
538
  import socket
390
539
  from hashlib import md5
391
540
  try:
392
541
  local_ip = socket.gethostbyname('localhost')
393
542
  except socket.gaierror:
394
543
  local_ip = '127.0.0.1' # 默认回退到 localhost 地址
395
- return md5(local_ip.encode('utf-8')).hexdigest()
396
- network_hash = get_network_hash()
544
+ return md5(f"{local_ip}:{oversea}".encode('utf-8')).hexdigest()
545
+
546
+ def configToSupportSubdomain(config):
547
+ if config["type"] == "s3":
548
+ s3_config = config["config"]
549
+ return {"type": "s3", "config": s3_config, "domain": s3_config["domain"]}
550
+ if config["type"] == "oss":
551
+ return {"type": "oss", "config": config["config"], "domain": config["config"]["domain"]}
552
+ if config["type"] == "ftp":
553
+ ftp_config = config["config"]
554
+ if _can_connect_ftp(ftp_config["host"], ftp_config["port"], ftp_config["username"], ftp_config["password"], ftp_config["writepath"]):
555
+ return {"type": "ftp", "config": ftp_config, "domain": ftp_config["readpath"]}
556
+ return None
557
+
558
+ oversea = is_oversea_network()
559
+ network_hash = get_network_hash(oversea)
560
+
397
561
  def _getFirstSupportSubdomain():
562
+ if oversea is not None:
563
+ for domain, config in DOMAIN_CONFIG.items():
564
+ if config.get("oversea", None) != oversea:
565
+ continue
566
+ support = configToSupportSubdomain(config)
567
+ if support:
568
+ support["oversea"] = oversea
569
+ return support
570
+
398
571
  for domain, config in DOMAIN_CONFIG.items():
399
572
  if config["type"] == "oss":
400
573
  return {"type": "oss", "config": config["config"], "domain": config["config"]["domain"]}
@@ -657,4 +830,4 @@ def downloadDir(url, saveDir, useCount=-1, autoDelete=False):
657
830
  print(f"url {url} not support")
658
831
  except Exception as e:
659
832
  print(f"downloadDir fail: {e}")
660
- return None
833
+ return None
@@ -1,12 +1,12 @@
1
1
  import uuid
2
2
  import platform
3
- import subprocess
4
- import os
5
- import sys
6
- import requests
7
- import time
8
- from io import BytesIO
9
- import psutil
3
+ import subprocess
4
+ import os
5
+ import sys
6
+ import requests
7
+ import time
8
+ from io import BytesIO
9
+ import psutil
10
10
  try:
11
11
  import pynvml
12
12
  except ImportError:
@@ -120,219 +120,219 @@ def generate_unique_id():
120
120
  unique_id = uuid.uuid5(uuid.NAMESPACE_DNS, mac + hostname)
121
121
  return str(unique_id).replace('-', '')
122
122
 
123
- def getOssImageSize(p):
124
- try:
125
- s = requests.session()
123
+ def getOssImageSize(p):
124
+ try:
125
+ s = requests.session()
126
126
  s.keep_alive = False
127
127
  res = s.get(p, timeout=60)
128
128
  image = Image.open(BytesIO(res.content), "r")
129
129
  s.close()
130
130
  return image.size
131
- except:
132
- return 0, 0
133
-
134
- def _round_float(value, digits=2):
135
- try:
136
- return round(float(value), digits)
137
- except:
138
- return None
139
-
140
- def _percent(used, total):
141
- try:
142
- if not total:
143
- return 0
144
- return round((float(used) / float(total)) * 100, 2)
145
- except:
146
- return 0
147
-
148
- def _load_average(logical_count):
149
- try:
150
- load1, load5, load15 = os.getloadavg()
151
- normalized = _percent(load1, logical_count or 1)
152
- return {
153
- "1m": _round_float(load1),
154
- "5m": _round_float(load5),
155
- "15m": _round_float(load15),
156
- "percent_1m": normalized
157
- }
158
- except:
159
- return {
160
- "1m": None,
161
- "5m": None,
162
- "15m": None,
163
- "percent_1m": None
164
- }
165
-
166
- def _decode_gpu_name(name):
167
- try:
168
- if isinstance(name, bytes):
169
- return name.decode("utf-8")
170
- return str(name)
171
- except:
172
- return "Unknown GPU"
173
-
174
- def _collect_gpu_info(G):
175
- gpu_info = {
176
- "count": 0,
177
- "list": [],
178
- "mem": [],
179
- "devices": []
180
- }
181
- try:
182
- if pynvml is None:
183
- raise ImportError("pynvml/nvidia-ml-py not available")
184
- pynvml.nvmlInit()
185
- gpu_count = pynvml.nvmlDeviceGetCount()
186
- gpu_info["count"] = gpu_count
187
- for i in range(gpu_count):
188
- handle = pynvml.nvmlDeviceGetHandleByIndex(i)
189
- name = _decode_gpu_name(pynvml.nvmlDeviceGetName(handle))
190
- mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
191
- used_gb = _round_float(mem_info.used / G)
192
- free_gb = _round_float(mem_info.free / G)
193
- total_gb = _round_float(mem_info.total / G)
194
- memory_percent = _percent(mem_info.used, mem_info.total)
195
- utilization_percent = None
196
- temperature_c = None
197
- power_w = None
198
- try:
199
- utilization_percent = pynvml.nvmlDeviceGetUtilizationRates(handle).gpu
200
- except:
201
- pass
202
- try:
203
- temperature_c = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
204
- except:
205
- pass
206
- try:
207
- power_w = _round_float(pynvml.nvmlDeviceGetPowerUsage(handle) / 1000)
208
- except:
209
- pass
210
- gpu_info["list"].append(f"GPU{i}: {name}")
211
- gpu_info["mem"].append(f"GPU{i}: total:{total_gb:.1f} G free:{free_gb:.1f} G")
212
- gpu_info["devices"].append({
213
- "index": i,
214
- "name": name,
215
- "memory_total_gb": total_gb,
216
- "memory_used_gb": used_gb,
217
- "memory_free_gb": free_gb,
218
- "memory_percent": memory_percent,
219
- "utilization_percent": utilization_percent,
220
- "temperature_c": temperature_c,
221
- "power_w": power_w
222
- })
223
- pynvml.nvmlShutdown()
224
- except Exception as e:
225
- gpu_info["error"] = str(e)
226
- try:
227
- pynvml.nvmlShutdown()
228
- except:
229
- pass
230
- return gpu_info
231
-
232
- def runtimeInfo():
233
- G = 1024 * 1024 * 1024
234
- logical_count = psutil.cpu_count() or 1
235
- virtual_mem = psutil.virtual_memory()
236
- disk = psutil.disk_usage("/")
237
- metrics = {
238
- "timestamp": int(time.time()),
239
- "cpu": {
240
- "percent": _round_float(psutil.cpu_percent(interval=0.1)),
241
- "logical_count": logical_count,
242
- "physical_count": psutil.cpu_count(logical=False),
243
- "load_avg": _load_average(logical_count)
244
- },
245
- "memory": {
246
- "total_gb": _round_float(virtual_mem.total / G),
247
- "available_gb": _round_float(virtual_mem.available / G),
248
- "used_gb": _round_float(virtual_mem.used / G),
249
- "free_gb": _round_float(virtual_mem.free / G),
250
- "percent": _round_float(virtual_mem.percent)
251
- },
252
- "disk": {
253
- "path": "/",
254
- "total_gb": _round_float(disk.total / G),
255
- "used_gb": _round_float(disk.used / G),
256
- "free_gb": _round_float(disk.free / G),
257
- "percent": _round_float(disk.percent)
258
- },
259
- "gpu": _collect_gpu_info(G),
260
- "task_slots": {
261
- "max": 1,
262
- "cur": 0,
263
- "idle": 1,
264
- "usage_percent": 0,
265
- "widget_power": {}
266
- }
267
- }
268
- try:
269
- from ryry.shared_memory import shared_memory_service
270
- max_counter = shared_memory_service.get_max_counter()
271
- cur_counter = shared_memory_service.get_cur_counter()
272
- widget_power = shared_memory_service.get_all_widget_power()
273
- if max_counter >= 999 and cur_counter == 0 and not widget_power:
274
- max_counter = 1
275
- idle_counter = max(0, max_counter - cur_counter)
276
- metrics["task_slots"] = {
277
- "max": max_counter,
278
- "cur": cur_counter,
279
- "idle": idle_counter,
280
- "usage_percent": _percent(cur_counter, max_counter),
281
- "widget_power": widget_power
282
- }
283
- except:
284
- pass
285
- return metrics
286
-
287
- def deviceInfo():
288
- mac = get_mac_address()
289
- mac = "" if mac == None else mac
290
- cpu_serial = get_cpu_serial()
291
- cpu_serial = "" if cpu_serial == None else cpu_serial
292
- hostname = get_hostname()
293
- G=1024*1024*1024
294
- cpu_freq = None
295
- cpu_cur_freq = None
296
- try:
297
- freq = psutil.cpu_freq()
298
- if freq:
299
- cpu_freq = freq.max / 1000 if freq.max else None
300
- cpu_cur_freq = freq.current / 1000 if freq.current else None
301
- except:
302
- pass
303
-
304
- virtual_mem = psutil.virtual_memory()
305
- disk = psutil.disk_usage("/")
306
- runtime = runtimeInfo()
307
- data = {
308
- "cpu": {
309
- "logical_count" : psutil.cpu_count(),
310
- "count" : psutil.cpu_count(logical=False),
311
- "max_freq" : f"{cpu_freq} GHz" if cpu_freq else "None",
312
- "current_freq" : f"{cpu_cur_freq} GHz" if cpu_cur_freq else "None",
313
- "percent" : runtime["cpu"].get("percent"),
314
- "load_avg": runtime["cpu"].get("load_avg")
315
- },
316
- "disk": {
317
- "total": f"{(disk.total / G):.1f} G",
318
- "used": f"{(disk.used / G):.1f} G",
319
- "free": f"{(disk.free / G):.1f} G",
320
- "percent": f"{disk.percent}%"
321
- },
322
- "memory": {
323
- "total": f"{(virtual_mem.total / G):.1f} G",
324
- "available": f"{(virtual_mem.available / G):.1f} G",
325
- "used": f"{(virtual_mem.used / G):.1f} G",
326
- "free": f"{(virtual_mem.free / G):.1f} G",
327
- "percent": f"{virtual_mem.percent}%"
328
- },
329
- "gpu": runtime["gpu"],
330
- "device_id": generate_unique_id(),
331
- "host_name": hostname,
332
- "task_slots": runtime["task_slots"],
333
- "timestamp": runtime["timestamp"]
334
- }
335
- return data
131
+ except:
132
+ return 0, 0
133
+
134
+ def _round_float(value, digits=2):
135
+ try:
136
+ return round(float(value), digits)
137
+ except:
138
+ return None
139
+
140
+ def _percent(used, total):
141
+ try:
142
+ if not total:
143
+ return 0
144
+ return round((float(used) / float(total)) * 100, 2)
145
+ except:
146
+ return 0
147
+
148
+ def _load_average(logical_count):
149
+ try:
150
+ load1, load5, load15 = os.getloadavg()
151
+ normalized = _percent(load1, logical_count or 1)
152
+ return {
153
+ "1m": _round_float(load1),
154
+ "5m": _round_float(load5),
155
+ "15m": _round_float(load15),
156
+ "percent_1m": normalized
157
+ }
158
+ except:
159
+ return {
160
+ "1m": None,
161
+ "5m": None,
162
+ "15m": None,
163
+ "percent_1m": None
164
+ }
165
+
166
+ def _decode_gpu_name(name):
167
+ try:
168
+ if isinstance(name, bytes):
169
+ return name.decode("utf-8")
170
+ return str(name)
171
+ except:
172
+ return "Unknown GPU"
173
+
174
+ def _collect_gpu_info(G):
175
+ gpu_info = {
176
+ "count": 0,
177
+ "list": [],
178
+ "mem": [],
179
+ "devices": []
180
+ }
181
+ try:
182
+ if pynvml is None:
183
+ raise ImportError("pynvml/nvidia-ml-py not available")
184
+ pynvml.nvmlInit()
185
+ gpu_count = pynvml.nvmlDeviceGetCount()
186
+ gpu_info["count"] = gpu_count
187
+ for i in range(gpu_count):
188
+ handle = pynvml.nvmlDeviceGetHandleByIndex(i)
189
+ name = _decode_gpu_name(pynvml.nvmlDeviceGetName(handle))
190
+ mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
191
+ used_gb = _round_float(mem_info.used / G)
192
+ free_gb = _round_float(mem_info.free / G)
193
+ total_gb = _round_float(mem_info.total / G)
194
+ memory_percent = _percent(mem_info.used, mem_info.total)
195
+ utilization_percent = None
196
+ temperature_c = None
197
+ power_w = None
198
+ try:
199
+ utilization_percent = pynvml.nvmlDeviceGetUtilizationRates(handle).gpu
200
+ except:
201
+ pass
202
+ try:
203
+ temperature_c = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
204
+ except:
205
+ pass
206
+ try:
207
+ power_w = _round_float(pynvml.nvmlDeviceGetPowerUsage(handle) / 1000)
208
+ except:
209
+ pass
210
+ gpu_info["list"].append(f"GPU{i}: {name}")
211
+ gpu_info["mem"].append(f"GPU{i}: total:{total_gb:.1f} G free:{free_gb:.1f} G")
212
+ gpu_info["devices"].append({
213
+ "index": i,
214
+ "name": name,
215
+ "memory_total_gb": total_gb,
216
+ "memory_used_gb": used_gb,
217
+ "memory_free_gb": free_gb,
218
+ "memory_percent": memory_percent,
219
+ "utilization_percent": utilization_percent,
220
+ "temperature_c": temperature_c,
221
+ "power_w": power_w
222
+ })
223
+ pynvml.nvmlShutdown()
224
+ except Exception as e:
225
+ gpu_info["error"] = str(e)
226
+ try:
227
+ pynvml.nvmlShutdown()
228
+ except:
229
+ pass
230
+ return gpu_info
231
+
232
+ def runtimeInfo():
233
+ G = 1024 * 1024 * 1024
234
+ logical_count = psutil.cpu_count() or 1
235
+ virtual_mem = psutil.virtual_memory()
236
+ disk = psutil.disk_usage("/")
237
+ metrics = {
238
+ "timestamp": int(time.time()),
239
+ "cpu": {
240
+ "percent": _round_float(psutil.cpu_percent(interval=0.1)),
241
+ "logical_count": logical_count,
242
+ "physical_count": psutil.cpu_count(logical=False),
243
+ "load_avg": _load_average(logical_count)
244
+ },
245
+ "memory": {
246
+ "total_gb": _round_float(virtual_mem.total / G),
247
+ "available_gb": _round_float(virtual_mem.available / G),
248
+ "used_gb": _round_float(virtual_mem.used / G),
249
+ "free_gb": _round_float(virtual_mem.free / G),
250
+ "percent": _round_float(virtual_mem.percent)
251
+ },
252
+ "disk": {
253
+ "path": "/",
254
+ "total_gb": _round_float(disk.total / G),
255
+ "used_gb": _round_float(disk.used / G),
256
+ "free_gb": _round_float(disk.free / G),
257
+ "percent": _round_float(disk.percent)
258
+ },
259
+ "gpu": _collect_gpu_info(G),
260
+ "task_slots": {
261
+ "max": 1,
262
+ "cur": 0,
263
+ "idle": 1,
264
+ "usage_percent": 0,
265
+ "widget_power": {}
266
+ }
267
+ }
268
+ try:
269
+ from ryry.shared_memory import shared_memory_service
270
+ max_counter = shared_memory_service.get_max_counter()
271
+ cur_counter = shared_memory_service.get_cur_counter()
272
+ widget_power = shared_memory_service.get_all_widget_power()
273
+ if max_counter >= 999 and cur_counter == 0 and not widget_power:
274
+ max_counter = 1
275
+ idle_counter = max(0, max_counter - cur_counter)
276
+ metrics["task_slots"] = {
277
+ "max": max_counter,
278
+ "cur": cur_counter,
279
+ "idle": idle_counter,
280
+ "usage_percent": _percent(cur_counter, max_counter),
281
+ "widget_power": widget_power
282
+ }
283
+ except:
284
+ pass
285
+ return metrics
286
+
287
+ def deviceInfo():
288
+ mac = get_mac_address()
289
+ mac = "" if mac == None else mac
290
+ cpu_serial = get_cpu_serial()
291
+ cpu_serial = "" if cpu_serial == None else cpu_serial
292
+ hostname = get_hostname()
293
+ G=1024*1024*1024
294
+ cpu_freq = None
295
+ cpu_cur_freq = None
296
+ try:
297
+ freq = psutil.cpu_freq()
298
+ if freq:
299
+ cpu_freq = freq.max / 1000 if freq.max else None
300
+ cpu_cur_freq = freq.current / 1000 if freq.current else None
301
+ except:
302
+ pass
303
+
304
+ virtual_mem = psutil.virtual_memory()
305
+ disk = psutil.disk_usage("/")
306
+ runtime = runtimeInfo()
307
+ data = {
308
+ "cpu": {
309
+ "logical_count" : psutil.cpu_count(),
310
+ "count" : psutil.cpu_count(logical=False),
311
+ "max_freq" : f"{cpu_freq} GHz" if cpu_freq else "None",
312
+ "current_freq" : f"{cpu_cur_freq} GHz" if cpu_cur_freq else "None",
313
+ "percent" : runtime["cpu"].get("percent"),
314
+ "load_avg": runtime["cpu"].get("load_avg")
315
+ },
316
+ "disk": {
317
+ "total": f"{(disk.total / G):.1f} G",
318
+ "used": f"{(disk.used / G):.1f} G",
319
+ "free": f"{(disk.free / G):.1f} G",
320
+ "percent": f"{disk.percent}%"
321
+ },
322
+ "memory": {
323
+ "total": f"{(virtual_mem.total / G):.1f} G",
324
+ "available": f"{(virtual_mem.available / G):.1f} G",
325
+ "used": f"{(virtual_mem.used / G):.1f} G",
326
+ "free": f"{(virtual_mem.free / G):.1f} G",
327
+ "percent": f"{virtual_mem.percent}%"
328
+ },
329
+ "gpu": runtime["gpu"],
330
+ "device_id": generate_unique_id(),
331
+ "host_name": hostname,
332
+ "task_slots": runtime["task_slots"],
333
+ "timestamp": runtime["timestamp"]
334
+ }
335
+ return data
336
336
 
337
337
  def process_is_alive(pid: int) -> bool:
338
338
  try:
@@ -385,6 +385,9 @@ def begin_restart(reason, update_cli=False, simple="https://pypi.python.org/simp
385
385
 
386
386
  K1 = "TFRBSTV0UkR3NXhocHc1eDlVdkZaUWsy"
387
387
  K2 = "TFVuYlRuZFRQMHpxYmU3TjVJSGw0YlVvdlJNQkR6"
388
+ RK11 = "NjM2OTJhNjIyYTc3MWZlY2ZmMDkxYmM5OGUxMzBjNGE="
389
+ RK22 = "MGUxM2E2MGQ0YjBiMDk3Yzc0NzIwOWNiZjdhM2I3ZjgxZWM3ODk1NTQzOGJmMjc1ZWJiY2QwMzM5ZjY0YmFjNw=="
390
+ RK23 = "aHR0cHM6Ly8xMTg2NDlkOGY5ZDA0MGFhN2M1NWU4ZGE5NWRlYWE0OS5yMi5jbG91ZGZsYXJlc3RvcmFnZS5jb20="
388
391
  def _decode_key(encoded_key):
389
392
  import base64
390
393
  try:
@@ -415,7 +418,6 @@ def _encode_key(raw_input):
415
418
  raise TypeError("Input must be str or bytes")
416
419
  return encoded.decode('utf-8')
417
420
 
418
-
419
421
  def check_restart():
420
422
  restart_file = os.path.join(constant.base_path, "restart")
421
423
  if os.path.exists(restart_file) == False:
@@ -495,4 +497,4 @@ nohup {restart_command} &''')
495
497
  result = subprocess.run(f"at now + 1 minutes -f {time_task_file} > {ot}", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
496
498
  print(f"{result.stdout}\n{result.stderr}")
497
499
  os.remove(restart_file)
498
- print("one minute later must be start!")
500
+ print("one minute later must be start!")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 6.18
3
+ Version: 6.19
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -3,7 +3,7 @@ import os
3
3
  import subprocess
4
4
  import datetime
5
5
 
6
- ryry_version = "6.18"
6
+ ryry_version = "6.19"
7
7
  cur_dir = os.path.dirname(os.path.abspath(__file__))
8
8
  constanspy = os.path.join(cur_dir, "ryry", "constant.py")
9
9
  try:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes