File size: 6,579 Bytes
e4f7cb6
ebc0a59
9000934
ad32dc4
9000934
ad32dc4
e4f7cb6
 
 
9000934
ad32dc4
 
 
 
 
 
e4f7cb6
ad32dc4
54ed867
ad32dc4
b3c5f99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1177697
b3c5f99
 
 
 
 
 
 
 
 
 
beefbaa
 
 
 
 
 
 
9000934
ad32dc4
 
 
54ed867
 
 
ebc0a59
 
54ed867
9000934
54ed867
 
 
 
 
 
 
9000934
ad32dc4
9000934
ad32dc4
54ed867
 
 
beefbaa
9000934
 
 
 
bbbd17f
9000934
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bbbd17f
 
ad32dc4
 
 
 
54ed867
ad32dc4
54ed867
 
 
 
 
ad32dc4
 
 
 
 
 
54ed867
 
 
 
 
ad32dc4
 
54ed867
ad32dc4
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import os
import urllib.parse
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
from pydantic import BaseModel

app = FastAPI()

# Configuração de CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

STORAGE_DIR = "/data"

if not os.path.exists(STORAGE_DIR):
    os.makedirs(STORAGE_DIR, exist_ok=True)

class UploadURLRequest(BaseModel):
    fileName: str
    contentType: str

class DeleteRequest(BaseModel):
    key: str

@app.get("/")
def read_root():
    return {"ok": True, "message": "Hugging Face storage node is running!", "storage": STORAGE_DIR}

@app.get("/health")
def health():
    return {"ok": True, "service": "huggingface-local-bucket"}

@app.get("/files")
def list_files():
    try:
        files = []
        for entry in os.scandir(STORAGE_DIR):
            if entry.is_file():
                if entry.name.startswith("."):
                    continue
                stat = entry.stat()
                files.append({
                    "fileName": entry.name,
                    "size": stat.st_size,
                    "lastModified": stat.st_mtime * 1000
                })
        files.sort(key=lambda x: x["lastModified"], reverse=True)
        return {"ok": True, "files": files, "bucket": "HuggingFace (Mount)"}
    except Exception as e:
        return {"ok": False, "error": str(e)}

@app.post("/upload-url")
def upload_url(payload: UploadURLRequest, request: Request):
    try:
        filename_encoded = urllib.parse.quote(payload.fileName)
        host_header = request.headers.get("host", "")
        if "hf.space" in host_header:
            base_url = f"https://{host_header}/"
        else:
            base_url = str(request.base_url)
            if base_url.startswith("http://") and "localhost" not in base_url and "127.0.0.1" not in base_url:
                base_url = base_url.replace("http://", "https://", 1)
        return {"ok": True, "url": f"{base_url}upload-file/{filename_encoded}"}
    except Exception as e:
        return {"ok": False, "error": str(e)}

@app.put("/upload-file/{filename}")
async def receive_upload(filename: str, request: Request):
    try:
        filename_decoded = urllib.parse.unquote(filename)
        filepath = os.path.join(STORAGE_DIR, filename_decoded)
        if not os.path.abspath(filepath).startswith(os.path.abspath(STORAGE_DIR)):
            raise HTTPException(status_code=400, detail="Caminho inválido")
        with open(filepath, "wb") as f:
            async for chunk in request.stream():
                f.write(chunk)
        return {"ok": True}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# 🎬 NOVA ROTA DE DOWNLOAD COM SUPORTE A STREAMING REAL (RANGE REQUESTS)
@app.get("/download")
def download_file(name: str, request: Request):
    try:
        filepath = os.path.join(STORAGE_DIR, name)
        if not os.path.exists(filepath):
            raise HTTPException(status_code=404, detail="Arquivo não encontrado")
        
        file_size = os.path.getsize(filepath)
        range_header = request.headers.get("range", "")
        
        # Define o tipo de mídia adequado
        lower_name = name.lower()
        media_type = "video/mp4" if lower_name.endswith(".mp4") else "application/octet-stream"
        if lower_name.endswith(".webm"): media_type = "video/webm"
        elif lower_name.endswith(".mkv"): media_type = "video/x-matroska"
        elif lower_name.endswith(".mp3"): media_type = "audio/mpeg"

        if range_header and range_header.startswith("bytes="):
            try:
                range_value = range_header.replace("bytes=", "").strip()
                if "-" in range_value:
                    start_str, end_str = range_value.split("-", 1)
                    start = int(start_str) if start_str else 0
                    end = int(end_str) if end_str else file_size - 1
                else:
                    start = int(range_value)
                    end = file_size - 1
            except ValueError:
                start = 0
                end = file_size - 1

            start = max(0, min(start, file_size - 1))
            end = max(start, min(end, file_size - 1))
            content_length = (end - start) + 1

            def file_iterator():
                with open(filepath, "rb") as f:
                    f.seek(start)
                    remaining = content_length
                    while remaining > 0:
                        chunk_size = min(4096 * 16, remaining)  # 64KB chunks
                        chunk = f.read(chunk_size)
                        if not chunk:
                            break
                        remaining -= len(chunk)
                        yield chunk

            headers = {
                "Content-Range": f"bytes {start}-{end}/{file_size}",
                "Accept-Ranges": "bytes",
                "Content-Length": str(content_length),
                "Cache-Control": "public, max-age=2592000",
            }
            return StreamingResponse(file_iterator(), status_code=206, headers=headers, media_type=media_type)
        
        # Caso não haja Range Request, envia normal equilibrando o cache
        response = FileResponse(filepath, media_type=media_type, filename=name)
        response.headers["Accept-Ranges"] = "bytes"
        return response
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/delete")
def delete_file(payload: DeleteRequest):
    try:
        filepath = os.path.join(STORAGE_DIR, payload.key)
        if os.path.exists(filepath):
            os.remove(filepath)
            return {"ok": True}
        return {"ok": False, "error": "Arquivo não encontrado"}
    except Exception as e:
        return {"ok": False, "error": str(e)}

@app.get("/preview/{filename:path}")
def preview_text(filename: str):
    try:
        filepath = os.path.join(STORAGE_DIR, filename)
        if not os.path.exists(filepath):
            return {"ok": False, "error": "Arquivo não encontrado"}
        with open(filepath, "rb") as f:
            content = f.read()
        try:
            text = content.decode('utf-8')
        except Exception:
            text = content.decode('latin-1')
        return {"ok": True, "content": text, "filename": filename}
    except Exception as e:
        return {"ok": False, "error": str(e)}