Skip to content

Commit bb02be0

Browse files
committed
fix: 修复 S3 存储没有正确响应的问题,修复 206 的问题
1 parent 0957c48 commit bb02be0

File tree

10 files changed

+81
-33
lines changed

10 files changed

+81
-33
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ python main.py
139139

140140
4. 开始 for Docker
141141
```bash
142-
docker run -d --restart=always -p 6543:6543 -v /path/to/your/config.yaml:/app/config.yaml -v /path/to/your/bmclapi:/app/bmclapi --name python-openbmclapi atianxiua/python-openbmclapi:latest
142+
docker run -d --restart=always -p 6543:6543 -v /path/to/your/config.yaml:opt/python-openbmclapi/config/config.yaml -v /path/to/your/bmclapi:opt/python-openbmclapi/bmclapi --name python-openbmclapi atianxiua/python-openbmclapi:latest
143143
```
144144

145145
# TODO

core/__init__.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import fastapi
1010

1111
from core import units
12-
from core.abc import ResponseFileLocal, ResponseFileMemory, ResponseFileRemote
12+
from core.abc import ResponseFileLocal, ResponseFileMemory, ResponseFileNotFound, ResponseFileRemote
1313

1414
from .locale import load_languages
1515
from .cluster import ClusterManager
@@ -126,7 +126,8 @@ async def download(request: fastapi.Request, hash: str, s: str, e: str, name: Op
126126
resp_headers = {}
127127
if name:
128128
resp_headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{urlparse.quote(name)}"
129-
129+
resp_headers["X-BMCLAPI-Hash"] = hash
130+
resp_headers["X-BMCLAPI-Size"] = str(size)
130131
clusters.hit(cluster_id, size or 0)
131132

132133
if isinstance(file, ResponseFileLocal):
@@ -143,25 +144,36 @@ async def download(request: fastapi.Request, hash: str, s: str, e: str, name: Op
143144
elif isinstance(file, ResponseFileMemory):
144145
result = b''
145146
r = utils.parse_range(range or "")
147+
if r is not None:
148+
if r.start >= len(file.data) or r.end is not None and (r.end > len(file.data) or r.end < r.start):
149+
return fastapi.responses.Response(
150+
status_code=416,
151+
content="Range Not Satisfiable"
152+
)
146153
status = 200
147154
if r is None:
148155
result = file.data
149156
elif r.end is None:
150157
result = file.data[r.start:]
151158
else:
152-
result = file.data[r.start:r.end]
159+
result = file.data[r.start:r.end + 1]
153160
if r is not None:
154161
status = 206
155162
resp_headers["Content-Range"] = f"bytes {r.start}-{len(result) + r.start - 1}/{len(file.data)}"
163+
resp_headers["Content-Length"] = str(len(result))
156164
return fastapi.responses.StreamingResponse(
157165
io.BytesIO(result),
158166
status_code=status,
159167
headers=resp_headers,
160168
)
169+
elif isinstance(file, ResponseFileNotFound):
170+
return fastapi.responses.Response(
171+
status_code=404,
172+
content="Not Found"
173+
)
161174
else:
162175
return fastapi.responses.Response(
163-
status_code=500,
164-
content="Internal Server Error"
176+
status_code=200,
165177
)
166178

167179
if cfg.access_log:

core/abc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,12 @@ def __init__(
121121

122122
def __repr__(self) -> str:
123123
return f'ResponseFileMemory(data={self.data}, size={self.size})'
124+
125+
class ResponseFileNotFound(ResponseFile):
126+
def __init__(
127+
self,
128+
):
129+
super().__init__(-1)
130+
131+
def __repr__(self) -> str:
132+
return f'ResponseFileNotFound(size={self.size})'

core/cluster.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,6 @@ async def _download_file(
471471
with tempfile.NamedTemporaryFile(
472472
dir=self._cache_dir,
473473
) as tmp_file:
474-
print(pbar.position)
475474
try:
476475
async with session.get(
477476
file.path

core/storage/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ async def get_missing_files(self, bmclapi_files: set[BMCLAPIFile], muitlpbar: ut
113113

114114
for file in await self.storage.list_download_files(muitlpbar):
115115
self.files[file.name] = file
116+
muitlpbar.update(1)
116117

117118
# start check
118119
# with concurreny

core/storage/abc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import anyio.abc
55

66
from core import utils
7-
from core.abc import BMCLAPIFile, ResponseFile, ResponseFileMemory, ResponseFileLocal, ResponseFileRemote
7+
from core.abc import BMCLAPIFile, ResponseFile, ResponseFileNotFound, ResponseFileMemory, ResponseFileLocal, ResponseFileRemote
88
from ..logger import logger
99

1010
RANGE = range(0x00, 0xFF + 1)

core/storage/alist.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async def _check(self):
8080
async with session.put(
8181
"/api/fs/put",
8282
headers={
83-
"File-Path": str(self._path / ".check"),
83+
"File-Path": str(self._path / ".py_check"),
8484
},
8585
data=str(time.perf_counter_ns())
8686
) as resp:
@@ -90,7 +90,7 @@ async def _check(self):
9090
data={
9191
"dir": str(self._path),
9292
"names": [
93-
".check"
93+
".py_check"
9494
]
9595
}
9696
) as resp:

core/storage/local.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import time
44
import anyio.abc
55

6-
from core.abc import ResponseFileLocal
6+
from core.abc import ResponseFile, ResponseFileLocal, ResponseFileNotFound
77
from ..logger import logger
88

99
from .abc import FileInfo, Storage
@@ -67,7 +67,7 @@ async def upload(
6767
async def _check(
6868
self,
6969
):
70-
file = Path(str(self.path)) / ".check"
70+
file = Path(str(self.path)) / ".py_check"
7171
while 1:
7272
try:
7373
file.write_text(str(time.perf_counter_ns()))
@@ -80,10 +80,10 @@ async def _check(
8080
self.emit_status()
8181
await anyio.sleep(60)
8282

83-
async def get_response_file(self, hash: str) -> ResponseFileLocal:
83+
async def get_response_file(self, hash: str) -> ResponseFile:
8484
p = Path(str(self.path)) / "download" / hash[:2] / hash
8585
if not p.exists():
86-
raise FileNotFoundError
86+
return ResponseFileNotFound()
8787
size = p.stat().st_size
8888
return ResponseFileLocal(
8989
size=size,

core/storage/s3.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import inspect
2+
from io import BytesIO
23
import tempfile
34
import time
45
from typing import Any
@@ -7,6 +8,8 @@
78
import anyio.to_thread
89
import aioboto3
910
import urllib.parse as urlparse
11+
12+
from core import logger
1013
from . import abc
1114
import cachetools
1215

@@ -60,9 +63,8 @@ def __init__(
6063
self.custom_s3_host = kwargs.get("custom_s3_host", "")
6164
self.public_endpoint = kwargs.get("public_endpoint", "")
6265
self.session = aioboto3.Session()
63-
self.list_lock = anyio.Lock()
64-
self.cache_list_bucket: dict[str, abc.FileInfo] = {}
65-
self.last_cache: float = 0
66+
self._cache: cachetools.TTLCache[str, abc.ResponseFile] = cachetools.TTLCache(maxsize=10000, ttl=60)
67+
self._cache_files: cachetools.TTLCache[str, abc.FileInfo] = cachetools.TTLCache(maxsize=10000, ttl=60)
6668
self._config = {
6769
"endpoint_url": self.endpoint,
6870
"aws_access_key_id": self.access_key,
@@ -80,11 +82,6 @@ async def setup(
8082

8183
self.task_group.start_soon(self._check)
8284

83-
async def list_bucket(
84-
self,
85-
):
86-
...
87-
8885
async def list_files(
8986
self,
9087
path: str
@@ -148,31 +145,61 @@ async def upload(
148145
obj = await bucket.Object(str(self.path / path))
149146
await obj.upload_fileobj(tmp_file)
150147
return True
151-
148+
152149

153150
async def _check(
154151
self,
155152
):
156153
while 1:
157154
try:
158-
await self.list_bucket()
155+
async with self.session.resource(
156+
"s3",
157+
endpoint_url=self.endpoint,
158+
aws_access_key_id=self.access_key,
159+
aws_secret_access_key=self.secret_key,
160+
region_name=self.region
161+
) as resource:
162+
bucket = await resource.Bucket(self.bucket)
163+
obj = await bucket.Object(str(self.path / ".py_check"))
164+
await obj.upload_fileobj(BytesIO(str(time.perf_counter_ns()).encode()))
165+
await obj.delete()
159166
self.online = True
160167
except:
161168
self.online = False
169+
logger.traceback()
162170
finally:
163171
self.emit_status()
164-
await anyio.sleep(300)
172+
await anyio.sleep(60)
165173

166174
async def get_response_file(self, hash: str) -> abc.ResponseFile:
167175
cpath = str(self.path / "download" / hash[:2] / hash)
168-
if cpath not in self.cache_list_bucket:
169-
return abc.ResponseFile(
170-
0
171-
)
176+
fileinfo = self._cache_files.get(hash)
177+
178+
if fileinfo is None:
179+
async with self.session.resource(
180+
"s3",
181+
endpoint_url=self.endpoint,
182+
aws_access_key_id=self.access_key,
183+
aws_secret_access_key=self.secret_key,
184+
region_name=self.region
185+
) as resource:
186+
bucket = await resource.Bucket(self.bucket)
187+
obj = await bucket.Object(cpath)
188+
info = await obj.get()
189+
fileinfo = abc.FileInfo(
190+
name=hash,
191+
size=info["ContentLength"],
192+
path=cpath
193+
)
194+
self._cache_files[hash] = fileinfo
195+
196+
if fileinfo is None:
197+
return abc.ResponseFileNotFound()
198+
172199
if self.custom_s3_host:
173200
return abc.ResponseFileRemote(
174201
f"{self.custom_s3_host}{cpath}",
175-
self.cache_list_bucket[cpath].size
202+
fileinfo.size
176203
)
177204
if self.public_endpoint:
178205
async with self.session.client(
@@ -204,7 +231,7 @@ async def get_response_file(self, hash: str) -> abc.ResponseFile:
204231
)
205232
return abc.ResponseFileRemote(
206233
url,
207-
self.cache_list_bucket[cpath].size
234+
fileinfo.size
208235
)
209236
async with self.session.resource(
210237
"s3",

core/storage/webdav.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ async def setup(self, task_group: TaskGroup):
4444
async def _check(self):
4545
while 1:
4646
try:
47-
await self.client.upload_to(io.BytesIO(str(time.perf_counter_ns()).encode()), ".check")
48-
await self.client.clean(".check")
47+
await self.client.upload_to(io.BytesIO(str(time.perf_counter_ns()).encode()), ".py_check")
48+
await self.client.clean(".py_check")
4949
self.online = True
5050
except:
5151
self.online = False

0 commit comments

Comments
 (0)