pyutilscripts 0.8.0__tar.gz → 0.9.0__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 (21) hide show
  1. {pyutilscripts-0.8.0/pyutilscripts.egg-info → pyutilscripts-0.9.0}/PKG-INFO +1 -1
  2. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyproject.toml +2 -1
  3. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts/__init__.py +1 -1
  4. pyutilscripts-0.9.0/pyutilscripts/httpd.py +105 -0
  5. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0/pyutilscripts.egg-info}/PKG-INFO +1 -1
  6. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts.egg-info/SOURCES.txt +1 -0
  7. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts.egg-info/entry_points.txt +1 -0
  8. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/LICENSE +0 -0
  9. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/README.md +0 -0
  10. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts/fcopy.py +0 -0
  11. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts/forward_tcp.py +0 -0
  12. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts/prunedirs.py +0 -0
  13. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts/toast.py +0 -0
  14. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts/utils/__init__.py +0 -0
  15. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts.egg-info/dependency_links.txt +0 -0
  16. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts.egg-info/requires.txt +0 -0
  17. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/pyutilscripts.egg-info/top_level.txt +0 -0
  18. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/setup.cfg +0 -0
  19. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/tests/test_action_parser.py +0 -0
  20. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/tests/test_fcopy.py +0 -0
  21. {pyutilscripts-0.8.0 → pyutilscripts-0.9.0}/tests/test_fcopy_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyutilscripts
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: PyUtilScripts 是一个基于 Python 的通用小工具集合,目标是提供编写通用任务的辅助工具。
5
5
  Author-email: Zero Kwok <zero.kwok@foxmail.com>
6
6
  License: MIT License
@@ -46,4 +46,5 @@ Issues = "https://github.com/ZeroKwok/pyutilscripts/issues"
46
46
  [project.scripts]
47
47
  "fcopy" = "pyutilscripts.fcopy:main"
48
48
  "prunedirs" = "pyutilscripts.prunedirs:main"
49
- "forward.tcp" = "pyutilscripts.forward_tcp:main"
49
+ "forward.tcp" = "pyutilscripts.forward_tcp:main"
50
+ "httpd" = "pyutilscripts.httpd:main"
@@ -2,7 +2,7 @@
2
2
  PyUtilScripts 是一个基于 Python 的通用小工具集合,目标是提供编写通用任务的辅助工具。
3
3
  """
4
4
 
5
- __version__ = "0.8.0"
5
+ __version__ = "0.9.0"
6
6
  __status__ = ""
7
7
  __author__ = "zero <zero.kwok@foxmail.com>"
8
8
 
@@ -0,0 +1,105 @@
1
+ #! python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import time
5
+ import argparse
6
+ import sys
7
+ import os
8
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
9
+ from socketserver import ThreadingMixIn
10
+
11
+ class RateLimitHandler(SimpleHTTPRequestHandler):
12
+ # Default value
13
+ SPEED_LIMIT = -1
14
+
15
+ def copyfile(self, source, outputfile):
16
+ """Override copyfile to implement rate limiting. If unlimited, use the original efficient method."""
17
+ # If unlimited, call parent class method (which uses shutil.copyfileobj internally and is more efficient)
18
+ if self.SPEED_LIMIT <= 0:
19
+ return super().copyfile(source, outputfile)
20
+
21
+ chunk_size = 1024 * 64 # 64KB chunk size
22
+ try:
23
+ start_time = time.time()
24
+ bytes_sent = 0
25
+ while True:
26
+ data = source.read(chunk_size)
27
+ if not data:
28
+ break
29
+ outputfile.write(data)
30
+ bytes_sent += len(data)
31
+
32
+ # Rate limiting calculation
33
+ elapsed = time.time() - start_time
34
+ expected_time = bytes_sent / self.SPEED_LIMIT
35
+
36
+ if elapsed < expected_time:
37
+ # 最小睡眠时间设为 0.5 秒, 因此理论最低速度约为 0.5 * 64kb = 128KB/s
38
+ time.sleep(min(expected_time - elapsed, 0.5))
39
+ except (ConnectionResetError, BrokenPipeError):
40
+ print(f"\n[!] Client {self.client_address[0]} disconnected")
41
+
42
+ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
43
+ daemon_threads = True
44
+
45
+ def parse_speed(speed_str):
46
+ """Parse speed string; support negative for unlimited."""
47
+ s = speed_str.upper()
48
+ try:
49
+ # Handle unlimited cases
50
+ if s == '-1' or s == '0' or s == 'UNLIMITED':
51
+ return -1
52
+
53
+ if s.endswith('MB'):
54
+ return int(float(s[:-2]) * 1024 * 1024)
55
+ elif s.endswith('KB'):
56
+ return int(float(s[:-2]) * 1024)
57
+ else:
58
+ return int(s)
59
+ except ValueError:
60
+ raise argparse.ArgumentTypeError("Invalid speed format. Use '1MB', '500KB', '-1' (unlimited), or a byte value.")
61
+
62
+ def main():
63
+ parser = argparse.ArgumentParser(description="Multithreaded HTTP file server with rate limiting")
64
+ parser.add_argument('--port', '-p', type=int, default=8000, help='Port to listen on (default: 8000)')
65
+ parser.add_argument('--bind', '-b', default='0.0.0.0', help='Bind address (default: 0.0.0.0)')
66
+ parser.add_argument('--limit', '-l', default='-1', help="Rate limit (e.g., 1MB, 500KB). Use -1 for unlimited")
67
+ parser.add_argument('--dir', '-d', default='.', help='Root directory to serve (default: current directory)')
68
+
69
+ args = parser.parse_args()
70
+
71
+ # 1. Verify directory exists
72
+ if not os.path.isdir(args.dir):
73
+ print(f"[ERROR] Directory does not exist: {args.dir}")
74
+ sys.exit(1)
75
+
76
+ # 2. Change to and record absolute path
77
+ abs_path = os.path.abspath(args.dir)
78
+ os.chdir(abs_path)
79
+
80
+ # 3. Parse rate limit
81
+ limit_bps = parse_speed(args.limit)
82
+ limit_display = f"{args.limit}/s ({limit_bps})" if limit_bps > 0 else "Unlimited"
83
+
84
+ # 4. Dynamic handler class
85
+ HandlerClass = type('CustomHandler', (RateLimitHandler,), {'SPEED_LIMIT': limit_bps})
86
+
87
+ server_address = (args.bind, args.port)
88
+ httpd = ThreadedHTTPServer(server_address, HandlerClass)
89
+
90
+ print(f"{'='*45}")
91
+ print(f"[*] HTTP server started")
92
+ print(f"[*] Listening on: http://{args.bind}:{args.port}")
93
+ print(f"[*] Serving directory: {abs_path}")
94
+ print(f"[*] Speed limit: {limit_display}")
95
+ print(f"{'='*45}")
96
+ print("[!] Press Ctrl+C to stop the server\n")
97
+
98
+ try:
99
+ httpd.serve_forever()
100
+ except KeyboardInterrupt:
101
+ print("\n[!] Shutting down server...")
102
+ httpd.server_close()
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyutilscripts
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: PyUtilScripts 是一个基于 Python 的通用小工具集合,目标是提供编写通用任务的辅助工具。
5
5
  Author-email: Zero Kwok <zero.kwok@foxmail.com>
6
6
  License: MIT License
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  pyutilscripts/__init__.py
5
5
  pyutilscripts/fcopy.py
6
6
  pyutilscripts/forward_tcp.py
7
+ pyutilscripts/httpd.py
7
8
  pyutilscripts/prunedirs.py
8
9
  pyutilscripts/toast.py
9
10
  pyutilscripts.egg-info/PKG-INFO
@@ -1,4 +1,5 @@
1
1
  [console_scripts]
2
2
  fcopy = pyutilscripts.fcopy:main
3
3
  forward.tcp = pyutilscripts.forward_tcp:main
4
+ httpd = pyutilscripts.httpd:main
4
5
  prunedirs = pyutilscripts.prunedirs:main
File without changes
File without changes
File without changes