Skip to content

Commit 59eb390

Browse files
committed
Add file management features: implement file upload, download, and info retrieval functionalities
1 parent bcf97e3 commit 59eb390

File tree

4 files changed

+323
-25
lines changed

4 files changed

+323
-25
lines changed

StealthIM/apis/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from . import util
44
from . import group
55
from . import message
6+
from . import file

StealthIM/apis/file.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import dataclasses
2+
import aiohttp
3+
import json
4+
import struct
5+
import blake3
6+
import os
7+
8+
from markdown_it.common.html_blocks import block_names
9+
from mdit_py_plugins.myst_blocks.index import block_break
10+
11+
from StealthIM import logger
12+
13+
from .common import request, Result, NoValResult
14+
15+
16+
BLOCK_SIZE = 2048 * 1024 # 2MB
17+
18+
19+
@dataclasses.dataclass
20+
class FileInfoResult:
21+
size: int
22+
result: Result
23+
24+
25+
async def get_file_info(
26+
url: str,
27+
session: str,
28+
file_hash: str,
29+
) -> FileInfoResult:
30+
api_address = f"{url}/api/v1/file/{file_hash}"
31+
logger.debug(f"Called API get_file_info with url: {api_address}")
32+
header = {
33+
"Authorization": f"Bearer {session}"
34+
}
35+
36+
response_data = await request(api_address, "POST", headers=header)
37+
logger.debug(f"Response data: {response_data}")
38+
39+
success = response_data["result"]["code"] == 800
40+
41+
return FileInfoResult(
42+
size=response_data["size"] if success else None,
43+
result=Result(
44+
code=response_data["result"]["code"],
45+
msg=response_data["result"]["msg"]
46+
)
47+
)
48+
49+
50+
async def upload_file(
51+
url: str,
52+
session: str,
53+
groupid: int,
54+
filename: str,
55+
file_path: str
56+
) -> dict:
57+
api_address = f"{url}/api/v1/file/"
58+
api_address = api_address.replace("https", "wss").replace("http", "ws")
59+
logger.debug(f"Called API upload_file with url: {api_address}")
60+
header = {
61+
"Authorization": f"Bearer {session}"
62+
}
63+
64+
file_size = os.path.getsize(file_path)
65+
block_hashes = []
66+
with open(file_path, 'rb') as f:
67+
while True:
68+
block = f.read(BLOCK_SIZE)
69+
if not block:
70+
break
71+
h = blake3.blake3(block).digest()
72+
block_hashes.append(h)
73+
final_hash = blake3.blake3(b''.join(block_hashes)).hexdigest()
74+
async with aiohttp.ClientSession() as client:
75+
async with client.ws_connect(api_address, headers=header) as ws:
76+
meta = {
77+
"size": str(file_size),
78+
"groupid": str(groupid),
79+
"hash": final_hash,
80+
"filename": filename[:30]
81+
}
82+
await ws.send_str(json.dumps(meta))
83+
meta_resp = await ws.receive()
84+
meta_resp = json.loads(meta_resp.data)
85+
if meta_resp.get("result", {}).get("code") != 0:
86+
return meta_resp
87+
with open(file_path, 'rb') as f:
88+
block_id = 0
89+
while True:
90+
block = f.read(BLOCK_SIZE)
91+
if not block:
92+
break
93+
block_id_bytes = struct.pack('<I', block_id)
94+
data = block_id_bytes + block
95+
await ws.send_bytes(data)
96+
block_resp = await ws.receive()
97+
block_resp = json.loads(block_resp.data)
98+
if block_resp.get("result", {}).get("code") != 0:
99+
return block_resp
100+
block_id += 1
101+
complete_resp = await ws.receive()
102+
complete_resp = json.loads(complete_resp.data)
103+
return complete_resp
104+
105+
106+
async def download_file(
107+
url: str,
108+
session: str,
109+
file_hash: str,
110+
range_header: str = None
111+
):
112+
"""
113+
下载文件,按协议 yield (offset, binary_data)。
114+
offset = BlockID * BLOCK_SIZE
115+
BlockID == 0xffffffff 时结束。
116+
"""
117+
api_address = f"{url}/api/v1/file/{file_hash}"
118+
logger.debug(f"Called API download_file with url: {api_address}")
119+
headers = {
120+
"Authorization": f"Bearer {session}"
121+
}
122+
if range_header:
123+
headers["Range"] = range_header
124+
async with aiohttp.ClientSession() as client:
125+
async with client.get(api_address, headers=headers) as resp:
126+
while True:
127+
block_id_bytes = await resp.content.read(4)
128+
if not block_id_bytes or len(block_id_bytes) < 4:
129+
logger.debug("No more block_id bytes, breaking.")
130+
break
131+
block_id = struct.unpack('<I', block_id_bytes)[0]
132+
length_bytes = await resp.content.read(4)
133+
if not length_bytes or len(length_bytes) < 4:
134+
logger.debug("No more length bytes, breaking.")
135+
break
136+
length = struct.unpack('<I', length_bytes)[0]
137+
data = await resp.content.read(length)
138+
if block_id == 0xffffffff:
139+
logger.debug("Received end block, stopping download.")
140+
break
141+
offset = block_id * BLOCK_SIZE
142+
logger.debug(f"Yield block: id={block_id}, offset={offset}, length={length}")
143+
yield offset, data

0 commit comments

Comments
 (0)